ael*_*ndy 43 c compiler-construction gcc ghc
在整个网络上,我感觉为编译器编写C后端不再是一个好主意了.GHC的C后端不再被积极开发(这是我不支持的感觉).编译器的目标是C--或LLVM.
通常,我认为GCC是一个很好的老式成熟编译器,在优化代码方面表现很好,因此编译为C将使用GCC的成熟度来产生更好更快的代码.这不是真的吗?
我意识到这个问题在很大程度上取决于所编译语言的性质以及其他因素,以便获得更易于维护的代码.我正在寻找一个更为一般的答案(编译语言),专注于性能(无视代码质量,等等).如果答案包括解释为什么GHC偏离C以及为什么LLVM作为后端表现更好(参见本文)或编译器做任何其他我不了解的编译器的例子,我也会很高兴.
aug*_*tss 26
让我列出编译为C时遇到的两个最大问题.如果这是您的语言问题,取决于您具有哪种功能.
垃圾收集当你有垃圾收集时,你可能不得不在程序的任何一点中断常规执行,此时你需要访问指向堆的所有指针.如果你编译为C,你不知道那些指针可能在哪里.C负责局部变量,参数等.指针可能在堆栈上(或者可能在SPARC上的其他寄存器窗口中),但是没有对堆栈的真正访问.即使你扫描堆栈,哪些值是指针?LLVM实际上解决了这个问题(我不知道自从我从未使用过带有GC的LLVM).
尾调用许多语言假设尾调用工作(即,它们不会增加堆栈); 方案要求它,Haskell假定它.C不是这种情况.在某些情况下,您可以说服某些C编译器进行尾调用.但是你希望尾调用是可靠的,例如,当尾调用未知函数时.有笨拙的解决方法,如蹦床,但没有什么比较令人满意的.
Mat*_*att 24
虽然我不是编译专家,但我相信它归结为这样一个事实,即你在翻译成C语言时失去了一些东西而不是翻译成LLVM的中间语言.
如果你想一想编译到C的过程中,创建一个编译器翻译成C代码,那么C编译器翻译成中间表示(在内存中的AST),然后转换该机器码.C编译器的创建者可能花了很多时间来优化语言中的某些人造模式,但是你不太可能创建一个从源语言到C的足够奇特的编译器来模仿人类编写的方式码.C语言失去了保真度 - C编译器对原始代码的结构没有任何了解.为了获得这些优化,你实际上是回调你的编译器,试图生成C代码,C编译器知道如何在构建AST时进行优化.乱.
但是,如果直接转换为LLVM的中间语言,就像将代码编译为与机器无关的高级字节码,这类似于C编译器,使您可以准确指定其AST应包含的内容.从本质上讲,您删除了解析C代码并直接转到高级表示的中间人,通过减少翻译来保留代码的更多特性.
与性能相关的是,LLVM可以为动态语言做一些非常棘手的事情,比如在运行时生成二进制代码.这是即时编译的"酷"部分:它正在编写要在运行时执行的二进制代码,而不是被编译时创建的内容所困扰.
GHC退出旧C后端的部分原因是GHC生成的代码不是gcc特别优化的代码.因此,随着GHC的本机代码生成器变得越来越好,大量工作的回报也越来越少.从6.12开始,NCG的代码在极少数情况下仅比C编译代码慢,因此随着NCG在ghc-7中变得更好,没有足够的动力来保持gcc后端存活.LLVM是一个更好的目标,因为它更模块化,并且可以在将结果传递给它之前对其中间表示进行许多优化.
另一方面,最后我看了,JHC仍然产生了C和最终的二进制文件,通常是(仅限于)gcc.而JHC的二进制文件往往非常快.
所以,如果你能产生代码的C编译器处理好,这仍然是一个不错的选择,但它可能不值得了太多的跳铁圈产生好的C,如果你能很容易通过其他途径产生良好的可执行文件.
正如您所提到的,C是否是一种优秀的目标语言在很大程度上取决于您的源语言.因此,与LLVM或自定义目标语言相比,C有一些缺点:
垃圾收集:一种想要支持高效垃圾收集的语言需要知道干扰C的额外信息.如果分配失败,GC需要找到堆栈和寄存器中的哪些值是指针而哪些不是.由于寄存器分配器不在我们的控制之下,我们需要使用更昂贵的技术,例如将所有指针写入单独的堆栈.当尝试在C之上支持现代GC时,这只是众多问题中的一个.(请注意,LLVM在该领域仍然存在一些问题,但我听说它正在进行中.)
特征映射和特定于语言的优化:某些语言依赖于某些优化,例如,Scheme依赖于尾调用优化.现代C编译器可以做到这一点但不保证这样做可能会导致问题,如果程序依赖于此正确性.在C之上可能难以支持的另一个特性是协同例程.
C编译器也无法很好地优化大多数动态类型语言.例如,Cython将Python编译为C,但生成的C使用对许多通用函数的调用,即使最新的GCC版本也不太可能对其进行优化.即时编译ala PyPy/LuaJIT/TraceMonkey/V8更适合为动态语言提供良好的性能(代价是更高的实现工作量).
开发经验:拥有一个解释器或JIT也可以为开发人员提供更方便的体验 - 生成C代码,然后编译并链接它,肯定会更慢,更不方便.
也就是说,我仍然认为使用C作为新语言原型的编译目标是一个合理的选择.鉴于LLVM被明确设计为编译器后端,如果有充分的理由不使用LLVM,我只会考虑C. 但是,如果源级语言非常高级,则很可能需要更早的更高级别的优化传递,因为LLVM确实是非常低级的(例如,GHC在生成调用LLVM之前执行其大部分有趣的优化).哦,如果你是一种语言的原型,使用解释器可能是最简单的 - 只是尽量避免过多依赖于解释器实现的功能.
除了形成所有代码生成器质量的原因外,还有其他问题:
请注意,第4点也意味着您需要花时间在外部项目发展时保持工作.这段时间通常不会真正进入您的项目,并且由于项目更具动态性,因此多平台版本需要大量额外的发布工程来满足变更需求.
简而言之,从我所看到的,虽然这样的举动允许快速启动(为许多架构免费获得合理的代码生成器),但也有缺点.其中大多数都与失去控制和Windows支持*gix等*nix中心项目有关.(LLVM对于长期来说太新了,但是他们的言论听起来很像十年前的gcc).如果一个你非常依赖的项目保持一定的路线(比如GCC会非常慢),那么你就会陷入困境.
首先,确定您是否希望拥有严重的非*nix(OS X更加unixy)支持,或者只有具有Windows的mingw权宜之计的Linux编译器?许多编译器需要一流的Windows支持.
第二,产品必须如何成品?什么是主要受众?它是开源开发人员可以处理DIY工具链的工具,还是想要定位初学者市场(如许多第三方产品,例如RealBasic)?
或者你真的想为深度整合和完整工具链的专业人士提供全面的产品吗?
这三个都是编译器项目的有效指示.问问自己你的主要方向是什么,并且不要假设有更多选项可以及时获得.例如,评估九十年代早期选择成为GCC前端的项目.
基本上,unix的方式是扩展(最大化平台)
完整的套件(如VS和Delphi,后者最近也开始支持OS X并且过去支持linux)深入并尝试最大化生产力.(几乎完全支持Windows平台,深度集成)
第三方项目不太明确.他们更多地追求自雇程序员和小众商店.他们拥有较少的开发人员资源,但更好地管理和关注他们.
就我个人而言,我会编译为 C。这样你就拥有了一种通用的中间语言,并且不需要担心你的编译器是否支持每个平台。使用 LLVM 可能会获得一些性能提升(尽管我认为通过调整 C 代码生成以使其对优化器更加友好也可以实现相同的效果),但它会将您锁定为仅支持 LLVM 支持的目标,并且必须等待当您想要支持新的、旧的、不同的或模糊的东西时,LLVM 会添加一个目标。
尚未提出的一点是,你的语言与C有多接近?如果您正在编译一种相当低级的命令式语言,那么C的语义可能会与您正在实现的语言非常接近.如果是这种情况,它可能是一个胜利,因为用您的语言编写的代码可能类似于某人用C手写的代码.Haskell的C后端肯定不是这种情况,这也是C后端优化如此糟糕的一个原因.
反对使用C后端的另一个观点是,C的语义实际上并不像它们看起来那么简单.如果您的语言与C显着不同,使用C后端意味着您将不得不跟踪所有这些令人生气的复杂性,以及C编译器之间可能存在的差异.使用LLVM可能更容易,它具有更简单的语义,或者设计自己的后端,而不是跟踪所有这些.
| 归档时间: |
|
| 查看次数: |
4819 次 |
| 最近记录: |