编译器发展史

注:本文为编译器相关的两篇文章合辑。


编译器的「五个十年」发展史

云头条 2018 年 11 月 03 日 23:26

作者:自 20 世纪 70 年代就读伊利诺伊大学研究生院以来,Michael Wolfe 就一直主攻并行计算方面的语言和编译器。在此过程中,他与别人创办 Kuck and Associates(已被英特尔收购),在俄勒冈州研究生院(自与俄勒冈健康与科学大学合并以来)投身学术界,并在 PGI 开发高性能 Fortran(PGI 先被意法半导体收购,最近被英伟达收购)。如今大部分时间,他在一个为高度并行计算、尤其是为英伟达 GPU 加速器开发和改进 PGI 编译器的团队担任技术主管。

如果想了解我们在计算机架构和驱动计算机架构的编译器方面的现状,有必要看看编译器如何在六十年间由一种架构改用另一种架构。

先让目光回到 1957 年的第一个编译器 IBM Fortran。这是一项了不起的技术。如果你看看它的起源和取得的成果,付出的巨大努力是今天的人都无法想象的。

IBM 想要销售计算机,想要销售让更多的人能够进行编程的计算机。当时编程是用汇编语言完成的。这太难了,IBM 很清楚这一点。于是蓝色巨人希望人们有办法更快地编写程序,又不牺牲性能。Fortran 的开发人员(指发明 Fortran 的那些人,而不是发明使用编译器和语言的程序的那些人)希望利用如今所谓的高级编程语言编写的程序,提供尽可能接近手动调整的机器代码的性能。

说到编译器,你必须考虑三个 P:性能、生产力和可移植性。Fortran 的发明者拿机器代码的性能作了比较。生产力方面的好处是,程序员再也不必编写机器代码。我不知道 IBM 在可移植性方面的最初意图,不过你在 1957 年无法为软件获得专利权,IBM 也没有抱怨其他企业组织实现 Fortran。因此没过多久,市面上出现了来自其他供应商的面向其他机器的 Fortran 编译器。这立即为 Fortran 程序提供了机器代码无法想象的可移植性。

从这一刻起,编译器开始迅猛发展起来。让我们看看每个十年的情况。

第一个十年

在 20 世纪 60 年代(就在我出生前),计算机架构师和编译器编写者首先开始考虑并行性。即使那样,人们仍然认为计算机速度不够快,速度提升也不够快,觉得并行性有望解决这个问题。我们看到指令级并行性引入到了 Seymour Cray 公司的 CDC 6600 和 CDC 7600 以及 IBM System/ 360 Model 91 中。更为激进的做法是开发出了由伊利诺伊大学的研究人员设计并由 Burroughs 公司制造的 ILLIAC IV、Control Data Corp STAR-100 以及德州仪器(TI)Advanced Scientific Computer。CDC 和 TI 的系统是内存到内存的长向量机,而 ILLIAC IV 是我们今天所说的 SIMD 机器。ILLIAC 之所以功能有限,是由于没有主要的标量处理器,编程起来确实很难。面向 STAR-100 的 Fortran 编译器添加了用于描述长连续向量操作的语法。TI ASC 机器最值得关注,因为它拥有第一个自动向量化编译器。TI 做了一番了不起的工作,确实提升了当时编译器分析的最新水平。

第二个十年

20 世纪 70 年代,Cray-1 成为第一台商业上大获成功的超级计算机。它有众多的跟随者和模仿者,许多读者可能了如指掌。Cray 机器的成功很大程度上归功于引入了向量寄存器。就像标量寄存器一样,向量寄存器让程序可以对小小的数据向量执行许多操作,没必要从内存加载和存储到内存。Cray Research 还开发了一种非常大胆的向量化编译器。它在许多方面与早期的 TI 编译器类似,但 Cray 编译器拥有让它非常备受关注的附加功能。其中最重要的一项功能是能够为程序员提供编译器反馈。

如今开发人员编译程序时,如果程序含有语法错误,编译器将生成出错消息。程序员不断修复这些错误并重新编译,直到拥有一个正常运行的程序。如果开发人员希望程序运行得更快,可以启用编译器优化标志,好让生成的可执行文件运行得更快 —— 他们希望如此。在大多数计算机上,优化代码和非优化代码之间的性能可能相差两倍,通常差异小得多,比如相差 10%、20% 或者 50%。与之相比的是原始 Cray 机器上可用的向量指令集。在这种情况下,代码被 Cray 编译器优化和向量化后,程序员常常会看到性能提升 5 倍到 10 倍。程序员、尤其是高性能计算(HPC)程序员愿意做大量工作,以便将性能提升 5 倍或更高。

来自 Cray 编译器的反馈将告诉程序员它在第 110 行向量化了一个循环(loop),在第 220 行向量化了另一个循环,但是没有在第 230 行向量化循环,原因是第 235 行有无法被向量化的 I/O 语句或函数调用。或者,可能存在编译器无法分析的数组引用,或者某个数组的第二个下标中的一个未知变量阻碍了依赖项分析,因此该循环无法进行向量化。想要获得向量性能的 Cray 程序员格外注意这些消息。他们根据这种反馈修改了代码,可能从循环中取出 I/O 语句,或者将循环推入到子程序,或者修改数组引用以删除某个未知变量。有时他们会添加一个编译器指令,以便向编译器传达可以安全地进行向量化这一信息,即使编译器无法通过依赖项分析来确定这一点。

多亏了编译器的反馈,发生了三件事。

首先,更多的程序被向量化,更多的程序员得益于 Cray 向量性能。其次,Cray 程序员受过了培训,不再将 I/O 语句、条件语句和过程调用放入到循环的中间。他们明白一个步长(stride)的数据访问很重要,确保内部循环中的数组访问是一个步长。第三,用 Cray 编译器自动向量化的程序可以在来自 Alliant、Convex、Digital、富士通、日立、IBM 和 NEC 的许多类似的向量机上重新编译。所有这些系统都拥有带向量寄存器的向量处理器和自动向量化编译器,之前针对 Cray 优化的代码在所有这些机器上都可以进行向量化,并很顺畅地运行。简而言之,Cray 程序员终于实现了性能、生产力和可移植性这三个目标。

这个编译器反馈有多重要、培训整整一代 HPC 开发人员为向量机编程有多成功,怎么强调都不为过。每个使用它的人都很高兴。那时候,我还在伊利诺伊大学,我们考虑开一家立足于并行化编译器技术的公司。当然,我们的技术比别人的要好,因为我们是一流的学者。在 Cray 编译器的早期阶段,用户抱怨编译器无法对任何内容进行向量化,因而不得不重写代码。我们认为自己有望解决这些问题,于是开了这家小公司专门搞这一块。几年后,我们接触同样那样用户、展示我们用于并行化和向量化循环的工具时,他们回复自己不需要这类工具,因为 Cray 编译器已经向量化了所有循环。倒不是说 Cray 编译器变得更聪明,不过我确信它会渐渐变得更好。主要是程序员在如何编写可向量化的循环方面训练有素。

第三个十年

20 世纪 80 年代多处理得到了广泛的实施和使用。早些时候已有多处理器,包括 IBM System /370s 和 Burroughs 系统。但是 32 位单片微处理器的问世推动多处理技术进入了主流。要开一家计算机公司,你不再需要设计处理器 —— 可以径直购买。你没必要编写操作系统,可以购买 Unix 的许可。只需要有人全部组装起来、贴上铭牌。要是有编译器就好了。 Sequent、Encore 和 SGI 都构建了有一个微处理器、性能出色的系统,但如果可以让一批处理器并行运行,那就更好了。

不像大获成功的自动向量化,自动并行化基本上一败涂地。它适用于最内层循环,但要实现大幅的并行加速,通常需要对外层循环进行并行化。不过当然,外层循环增添了控制流的复杂性,常常包括过程调用,现在你的编译器分析完全崩溃了。一种确实可行的方法是,让程序员参与其中,分析并行性,并输入编译器指令来驱动它。我们由此看到了面向并行循环的各种指令集纷纷出现。Cray、Encore、IBM 和 Sequent 都有各自的指令集,唯一的共同点就是 SGI 采用 Sequent 指令。同样,许多可扩展的系统要传递网络消息,这促使开发了众多针对特定供应商的、学术性的消息传递库。

第四个十年

20 世纪 90 年代,所有那些消息传递库都被消息传递接口(MPI)取而代之,所有那些针对特定供应商的并行化指令都被 OpenMP 取而代之。出现了扩展性更强的并行系统,比如 Thinking Machines CM-5 以及有成千上万个商用微处理器的其他系统。基于微处理器的可扩展系统主要用 MPI 进行编程,但 MPI 是一个库,对编译器来说不透明。MPI 作为一种编程模型而具有的优点是,虽然通信开销很大,但通信在程序中是完全暴露的。MPI 程序员知道何时插入显式通信调用,他们不遗余力地尽可能减少和优化通信。缺点是,从编程模型的角度来看,它非常低级,MPI 程序员没有得到编译器的帮助。

OpenMP 诞生于针对特定供应商的并行指令集百花齐放的时期,因为用户需要它。用户们才不愿仅仅为了能在可供使用的所有不同系统上运行程序而将程序重写 12 次。OpenMP 的整个宗旨就是以同样的方式彰显 “并行性可以”。不像 MPI,编译器必须支持指令,因为指令是语言的一部分。你无法将 OpenMP 作为一个库来实现。

同样在 20 世纪 90 年代,市面上出现了面向单芯片微处理器的 SIMD 指令集,比如来自英特尔的 SSE 和 SSE2,我们看到编译器恢复了当时已有 20 年或 25 年历史的同样的向量化技术,以便自动利用那些 SIMD 指令。

第五个十年

2000 年后不久,众多供应商开始普遍提供多核微处理器。全世界突然意识到必须为大家解决这个并行编程问题。

当时,许多应用程序在一块芯片上的所有核心上以及分布式内存节点上使用扁平的 MPI 编程模型。你始终可以添加更多的 MPI 序号(rank)来使用更高的并行性。这一招效果有限,但是你开始获得大量序号时,一些 MPI 程序会遇到扩展问题。数据在每个 MPI 序号中复制时,内存使用会有点失控。一旦你开始在每个节点上放置一二十个核心,复制的每一项数据可供使用的内存量是 12 倍或 24 倍。如果是为单个节点编程,OpenMP 及其他共享内存并行编程模型开始受到追捧。比如在 20 世纪 90 年代,英特尔推出了线程构建模块(又名 TBB);有人声称,TBB 是如今最受欢迎的并行编程语言。

大概在同一时期,异构 HPC 系统开始出现,不过异构性其实不是新话题。我们在 20 世纪 60 年代就遇到了异构性,使用附加的协处理器用于数组处理。附加的处理器大受欢迎,原因是它们可以比 CPU 更快地完成专门操作。它们常常能够以小型机的价格提供大型机的性能,大约 15 年来浮点系统在这方面做得很好。在 21 世纪初期,有几款专门为 HPC 市场开发或加以改造的加速器,比如 ClearSpeed 加速器和 IBM Cell 处理器。这两款产品都取得了一些真正的技术成功,但是 HPC 市场规模太小,无法支持开发独立的定制处理器芯片。

这给 GPU 计算留下了缺口。GPU 相对定制加速器的优势在于,GPU 的主打功能很好 —— 早期的主要任务是图形和游戏,现在还包括 AI 和深度学习,因此从最新硅片技术和驱动硅片所需的软件方面来看,开发处理器非常高昂的成本维持得下去。

伴随异构性而来的是这个显而易见的问题:我们如何为这些系统编程?早在那时,浮点系统提供了在底层使用加速器的子程序库。程序员调用该库,HPC 用户可以高效地使用加速器,无需实际编程(不过有一些程序员进行了编程)。今天,想支持多年来在标量、向量和可扩展系统上开发的众多应用程序,HPC 开发人员需要能够为加速器高效地编程,他们希望程序看起来尽可能正常。使用 OpenCL 或 CUDA 可以为你提供了很强的控制性,但是对于现有 HPC 源代码的影响可能非常大。程序员通常将有待在加速器上运行的代码提取到特别注释的函数中,而且编写的方式常常与为主机编写的方式全然不同。这时候,专门为通用并行编程设计的基于指令的编程模型和语言就有一些优势,而优秀的优化编译器大有用场。

置身于平行世界

这给我们引出了另一个至关重要的方面。20 世纪 60 年代为指令级并行性开发的所有技术现在都在微处理器里面。20 世纪 70 年代的向量处理概念存在于微处理器里面的 SIMD 寄存器中。20 世纪 80 年代 Cray 和基于商用处理器的系统的多个处理器都以多个核心的形式存在于微处理器里面。今天的微处理器可以说整合了过去 50 年来所做的全部架构工作,而由于我们现在拥有出色的晶体管技术(数十亿个门),我们可以这么做。另外我们现在还有异构性;在一些情况下,我们甚至可以在同一块芯片上做到异构性。比如说,中国太湖之光系统中的 “神威” 芯片从封装的角度来看是一块芯片,但芯片上每个四分之一的部分都包括一个主处理器和 64 个计算处理器。因此,它基本上是异构的,编程起来更像是 CPU-GPU 混合体,而不是像多核处理器。

为了充分利用异构的 GPU 加速节点,程序需要具有很强的并行性,而且是类型合适的并行性。这些高度并行处理器不是通过更快的时钟提供性能,不是由于某种异常奇特的架构。确切地说,那是由于更多的可用门用于并行核心,而这些并行核心用于缓存、乱序执行或分支预测。商用 CPU 的所有那些主要任务被排除在 GPU 之外,结果是大规模并行处理器在合适的内核和应用程序上提供更高的吞吐量。

这让我们回到了三个 P,而性能不是 HPC 开发人员的唯一目标。程序员需要高性能、良好的生产力和广泛的可移植性。大多数程序员希望程序只编写一次,而不是多次。

在当时只有节点上的单处理器、所有并行性横跨节点的时期,程序员可以使用扁平的 MPI 来应付。这类程序可在一系列广泛的机器上顺畅运行,但这不再是我们现在所置身的 HPC 环境。我们在一个节点中有多个处理器,有 SIMD 指令,还有异构加速器,引入了更多类型的并行性。为了获得可移植性,我们需要能够编写针对不同数量的核心、不同的 SIMD 或向量长度可以高效映射的程序,以及同构或异构的系统,没必要每次改用新系统就要重写程序。为了确保生产力,我们需要把其中尽可能多的部分抽取出来。我们使用编译器为今天的 HPC 系统试图做到的是与 IBM 在六十年前使用第一个 Fortran 编译器做到同样的抽象质量,后者实际上是第一种任何类型的高级语言编译器。

目标是在如今极其复杂的硬件上获得尽可能接近手动编程的性能。今天,就小小的简单代码块而言,针对英伟达 GPU 的 PGI OpenACC 编译器常常可以提供接近原生 CUDA 的性能。对于像大型函数这种更复杂的代码序列,或者整批调用树被移植到 GPU 时,接近原生代码性能要困难得多。

客观地说,OpenACC 编译器天生处于劣势。在 CUDA 中重写代码时,程序员可能查明某个数据结构不适合 GPU,可能改变数据结构以增强并行性或性能。也许程序员查明程序逻辑的一部分不是很适合 GPU,或者查明数据访问模式并不理想,因此重写该逻辑或循环以改进数据访问模式。如果你在对应的 OpenACC 程序中进行同样这些更改,常常也会获得好得多的性能。但是你最好不这么做,如果重写减慢了多核 CPU 上代码的运行速度,更是如此。因此,OpenACC 代码可能无法获得与你花了这番编程工作量同样的性能级别。即便如此,如果 OpenACC 代码的性能足够接近 GPU 上的 CUDA,基于指令的编程模型在生产力和可移植性方面的好处常常很明显。

尽管过去的 60 年间我们在编译器技术方面取得了诸多进展,但一些人仍然认为编译器与其说是解决办法,还不如说是问题。他们想要的是来自编译器的可预测性,而不是在后台优化编译器的高级分析和代码转换。这可能引出了这条道路:我们尝试从编译器中获取功能,将更多的责任交到程序员的手里。有人会说,这正是 OpenMP 今天所走的道路;我看到的危险是,我们到头来可能为了可预测性而牺牲了可移植性和生产力。比如说,设想你仍然得使用英特尔的 SSE 和 AVX 内部函数,对每个循环进行手动向量化,而不是针对英特尔至强处理器上的 SIMD 指令进行自动向量化。你实际上在编写内联汇编代码。这具有很强的可预测性,但很少有程序员想要在该层面编写所有的计算密集型代码,你要为每一代 SIMD 指令重写代码,或者在非 X86 CPU 上使用 SIMD 指令时更是如此。

计算机架构方面任何合理的进步都伴随编译器技术方面合理的进步。不能仅用那些架构和编译器所提供的性能来衡量好处,还要用程序从一代 HPC 系统移植到下一代(不必完全重写)后,节省的人力和提高的生产力来衡量。


常见编译器的历史来源

itianyi 于 2015-01-23 16:19:21

很多时候,出现一些类似 GNU,GCC,CLANG,LLVM 等与编译器有关的名词的时候,都不太清楚它到底是干嘛的,理解这些东西后,对于 xcode 中很多配置型的需求修改起来都会得心应手,因此有必要了解透彻他们直接的关系与区别。

1,GNU

先看看 wiki 百科上的官方说明:

“GNU,名称来自 Gnu’s Not Unix" 的缩写,一个类 UNIX 的操作系统,由 GNU 计划推动,目标在于创建一个完全兼容于 UNIX 的自由软件环境。”

由于当时 UNIX 系统是商业软件,是收费的,而且有一部分源码是没有开放的,所以在 1983 年,理查德・斯托曼提出 GNU 计划,希望发展出一套完整的开放源代码操作系统来取代 Unix,计划中的操作系统,名为 GNU。

因此,GNU 的出现的目的就是为了取代 UNIX 系统。

但是操作系统是包括很多软件的,除了操作系统内核之外,还要有编辑器,编译器,shell 等等一些软件来支持。

1989 年,GNU 项目中的其他部份,如编辑器、编译器、shell 等都已经完成,独缺操作系统核心。1990 年,自由软件基金会开始正式发展 Hurd,作为 GNU 项目中的操作系统。

注意:linux 并不是 GNU 计划的一部分。linux 只是使用了许多 GNU 计划软件 (包括 GCC 编译器,文本编译器等)。

1991 年,Linux 出现,所有 GNU 项目中,运行于用户空间的软件,都可以在 Linux 上使用。许多开发者转向于 Linux,Linux 成为常见的 GNU 计划软件运行平台。理查德・斯托曼主张,Linux 操作系统使用了许多 GNU 计划软件,应正名为 GNU/Linux,但没有得到 Linux 社区的一致认同,形成 GNU/Linux 命名争议。

1992 年,Linux 与其他 GNU 软件结合,完全自由的操作系统正式诞生。许多程序员参与了 Linux 的开发与修改,也经常将 Linux 当成开发 GNU 计划软件的平台。该操作系统往往被称为 “GNU/Linux” 或简称 Linux。但 Linux 本身不属于 GNU 计划的一部份,GNU 计划自己的内核 Hurd 依然在开发中,但直到 2013 年为止,都还没有稳定版本发布。

GNU 工程十几年以来已经成为一个对软件开发主要的影响力量,创造了无数的重要的工具,例如:强健的编译器,有力的文本编辑器,甚至一个全功能的操作系统。这个工程是从 1984 年麻省理工学院的程序员理查德・斯托曼的想法得来的,他想要创建一个自由的、和 UNIX 类似的操作环境。从那时开始,许多程序员聚集起来开始开发一个自由的、高质量、易理解的软件。

GNU 计划采用了部分当时已经可自由使用的软件,例如 TeX 排版系统和 X Window 视窗系统等。不过 GNU 计划也开发了大批其他的自由软件,这些软件也被移植到其他操作系统平台上,例如 Microsoft Windows、BSD 家族、Solaris 及 Mac OS。

许多 UNIX 系统上也安装了 GNU 软件,因为 GNU 软件的质量比之前 UNIX 的软件还要好。

所以,GNU 计划中的许多软件目前在所有的操作系统中都应用广泛 (Unix,mac,linux,windows,bsd…),最出名的就是 GCC 了

总结

GNU 计划本来是为了开发一个自由系统来取代 UNIX 的,但是由于开发的内核 hurd 一直不怎么样,这个系统至今都没出稳定版本,然而 GNU 计划中开发的其他一些自由软件,比如 GCC 编译器,却非常的好,在移植到各大操作系统上一直广泛使用至今。

注意一点

文中说的自由软件,千万别与免费软件混淆了,自由是说你可以自由的使用,当然前提是获得了源码才能自由的使用。

比如,你在一个应用里面使用了 gcc 计划的软件,你卖自己的应用多少钱你自己定。免费的软件不一定是开源的。

2,GCC

先看看 wiki 百科上的官方说明:

“ GCC (GNU Compiler Collection,GNU 编译器套装),是一套由 GNU 开发的编程语言编译器。它是一套以 GPL 及 LGPL 许可证所发布的自由软件,也是 GNU 项目的关键部分,亦是自由的类 Unix 及苹果电脑 Mac OS X 操作系统的标准编译器。GCC (特别是其中的 C 语言编译器) 也常被认为是跨平台编译器的事实标准。

GCC 原名为 GNU C 语言编译器 (GNU C Compiler),因为它原本只能处理 C 语言。GCC 很快地扩展,变得可处理 C++。之后也变得可处理 Fortran、Pascal、Objective-C、Java、Ada,以及 Go 与其他语言。

原本用 C 开发,后来因为 LLVM、Clang 的崛起,令 GCC 更快将开发语言转换为 C++。许多 C 的爱好者在对 C++ 一知半解的情况下主观认定 C++ 的性能一定会输给 C,但是 Taylor 给出了不同的意见,并表明 C++ 不但性能不输给 C,而且能设计出更好,更容易维护的程序 ”

由于 GCC 已成为 GNU 系统的官方编译器 (包括 GNU/Linux 家族),它也成为编译与创建其他操作系统的主要编译器,包括 BSD 家族、Mac OS X、NeXTSTEP 与 BeOS。

GCC 通常是跨平台软件的编译器首选。有别于一般局限于特定系统与运行环境的编译器,GCC 在所有平台上都使用同一个前端处理程序,产生一样的中介码,因此此中介码在各个其他平台上使用 GCC 编译,有很大的机会可得到正确无误的输出程序。

总结

mac 之前的 cocoa 框架便是用 GCC 编译的,所以 ios 与 mac os 都是默认使用的 GCC 编译器 (现在是 clang 与 llvm,下面会有介绍)

android 的系统层因为是 linux 内核,自然也是 GCC 编译的,但是 android 的 app 因为是运行在 Dalvik 虚拟机,所以用的不是 GCC。

windows 的应用,大部分都是使用的 vs 系列的编译器,毕竟是 windows 自家的编译器,用到 GCC 的不多。

3,Clang

先看看 wiki 百科上的官方说明:

http://zh.wikipedia.org/wiki/Clang

“ Clang 是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端。它采用了底层虚拟机 (LLVM) 作为其后端。
它的目标是提供一个 GNU 编译器套装 (GCC) 的替代品。Clang 项目包括 Clang 前端和 Clang 静态分析器等。
这个软件项目在 2005 年由苹果电脑发起,是 LLVM 编译器工具集的前端 (front-end),目的是输出代码对应的抽象语法树 (Abstract Syntax Tree, AST),并将代码编译成 LLVM Bitcode。接着在后端 (back-end) 使用 LLVM 编译成平台相关的机器语言 。Clang 支持 C、C++、Objective C。

Clang 本身性能优异,其生成的 AST(Abstract Syntax Tree)所耗用掉的内存仅仅是 GCC 的 20% 左右。FreeBSD 10 将 Clang/LLVM 作为默认编译器。

测试证明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍,还能针对用户发生的编译错误准确地给出建议。

总结

GCC 目前作为跨平台编译器来说它的兼容性无异是最强的,兼容最强肯定是以牺牲一定的性能为基础的,苹果为了提高性能,因此专门针对 mac 系统开发了专用的编译器 clang 与 llvm,clang 用于编译器前段,llvm 用于后端。

3,LLVM

先看看 wiki 百科上的官方说明:http://zh.wikipedia.org/wiki/LLVM

“ LLVM,它是一个编译器的基础建设,以 C++ 写成。它是为了任意一种编程语言写成的程序,利用虚拟技术,创造出编译时期,链结时期,运行时期以及 “闲置时期” 的优化。

在 Xcode4 之后,苹果将 Xcode 的默认编译器变成了 LLVM。

LLVM 历史

Apple (包括中后期的 NeXT) 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编译器标准一直做得不错,但 Apple 对编译工具会提出更高的要求。

一方面,是 Apple 对 Objective-C 语言 (甚至后来对 C 语言) 新增很多特性,但 GCC 开发者并不买 Apple 的帐 —— 不给实现,因此索性后来两者分成两条分支分别开发,这也造成 Apple 的编译器版本远落后于 GCC 的官方版本。

另一方面,GCC 的代码耦合度太高,不好独立,而且越是后期的版本,代码质量越差,但 Apple 想做的很多功能 (比如更好的 IDE 支持) 需要模块化的方式来调用 GCC,但 GCC 一直不给做,从根本上限制了 LLVM-GCC 的开发。所以,这种不和让 Apple 一直在寻找一个高效的、模块化的、协议更放松的开源替代品,于是 Apple 请来了编译器高材生 Chris Lattner, LLVM 就这样产生了。

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++ 的支持也热火朝天地进行着。

更详细的原因:

Xcode 编译器介绍:

http://www.cnblogs.com/ydhliphonedev/archive/2012/08/29/2661726.html

总结:

因为 GCC 的编译器已经慢慢无法满足苹果的需求,因此,苹果开发了 Clang 与 LLVM 来完全取代 GCC,Xcode4 之后,苹果的默认编译器已经是 LLVM 了。Clang 作为编译器前端,LLVM 作为编译器后端。

4,编译器相关知识

问题:苹果以 clang 作为编译器前端,llvm 作为编译器后端,那么编译器的前后端到底是什么东西呢?

我们先回到一个常识性的问题,什么是编译器呢?简单地说,编译器可以看作是一个语言翻译器。就像把中文翻译成英语一样,编译器可以把高级语言翻译成计算机能够执行的机器语言。这样看来,GCC 可以算得上是一个精通多国语言的高级翻译官了。

最简单的 GCC 使用指令如下所示:

gcc hello.c -o hello

GCC 接受 hello.c 作为输入,最后产生目标可执行代码 hello,这个简单的流程实际上经历了很多步骤。

虽然我们只用了一条命令就完成了编译,但实际上 gcc 命令依次呼叫了 cpp,gcc 自己,gas 以及 ld 来进行完整的编译流程,最后生成最终的可执行文件 hello。

学过编译原理这门课程的同学对下面这副图应该很熟悉,这是经典的编译流程。

下面以 GCC 编译器为例,GCC 作为经典的编译器,自然也是遵循这个教科书流程 (实际 GCC 的处理更复杂点,但本质上是一样的)。

我们先简化一下上面这幅图,以中间代码为分界,前面的词法分析、语法分析、语义分析我们把它称之为前端处理,后面的优化和目标代码生成我们称之为后端处理。

试想一下,是否可以为不同的高级语言单独写一个前端,然后为不同的处理器架构单独写一个后端呢?

GCC 基本上也是这么实现的,不过不要误会,并没有一个统一的 gcc 执行程序能够处理如此多的前端和后端,每个语言的编译器都是一个独立的程序 (如 C 语言的编译器是 gcc,C++ 的编译器是 g++),而不同的后端也要对应不同的可执行程序。你可以下载单独的一份 GCC 源代码,通过不同的 configure 来生成自己需要的编译器。

而且,编译器的实现也比上图要复杂的多,前端的主要功能是产生一个可供后端处理的语法树,而语法树结构实际上很难与处理器架构脱钩,这些都是编译器应用中需要解决的问题。

GCC 强大的真正原因

GCC 之所以强大,原因在于其真正的自由特性。GCC 是一款自由的编译器,允许用户随时根据需要修改代码进行定制和调整。

如果某个硬件平台新增了新的指令,而常规编译器无法生成这些指令,那么可以通过修改 GCC 的后端来支持这些指令。

如果觉得 C 语言功能不够丰富,想要增加一些功能,也可以通过修改 GCC 的前端来实现这些需求。

因为有了GCC,我们才拥有这些自由,以及迅速实现自己想法的能力,而这些才是GCC强大背后的基础。


via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值