编译器中间表示的设计哲学:从单层IR到多层次抽象的六十年演进
1957年,IBM的John Backus团队发布了第一个Fortran编译器。这个改变计算机科学历史的程序有一个鲜为人知的细节:它没有使用任何我们今天称为"中间表示"的东西。编译器直接从源代码生成机器码,所有优化都在生成过程中即时完成。这种方式在今天看来几乎不可想象,但它恰恰揭示了编译器设计中一个根本性的问题——为什么我们需要中间表示? ...
1957年,IBM的John Backus团队发布了第一个Fortran编译器。这个改变计算机科学历史的程序有一个鲜为人知的细节:它没有使用任何我们今天称为"中间表示"的东西。编译器直接从源代码生成机器码,所有优化都在生成过程中即时完成。这种方式在今天看来几乎不可想象,但它恰恰揭示了编译器设计中一个根本性的问题——为什么我们需要中间表示? ...
引言:优化的悖论 在软件开发领域,编译器优化常被视为理所当然的性能提升手段。程序员们习惯性地在编译命令中添加-O2或-O3,期望编译器施展魔法般的变换:消除冗余计算、内联函数调用、展开紧凑循环、向量化数值运算。这种信任建立在一个隐含的假设之上——更多的优化总是意味着更快的代码。 ...
1972年,David Gries在《Compiler Construction for Digital Computers》中描述了一个看似简单的优化:把被调用函数的代码直接复制到调用点。五十年后,这个"复制粘贴"技术仍然是编译器优化中最关键、最复杂,也最容易被误解的一环。 ...
每一个 C 程序员都遇到过这样的错误:undefined reference to 'foo' 或 multiple definition of 'bar'。编译通过了,但链接器拒绝了你的代码。那一刻,你可能只会机械地检查头文件、库路径或声明顺序,然后继续工作。但你是否想过,链接器究竟在做什么?为什么它能容忍某些重复定义,却对另一些报错?为什么同一个程序在静态链接和动态链接下行为不同? ...
1981年,IBM的研究员Gregory Chaitin面临一个棘手的问题:如何让PL.8编译器生成的代码更高效?当时,程序中的变量远多于处理器寄存器,编译器必须决定哪些变量驻留在寄存器,哪些被"驱逐"到内存。这个看似简单的资源分配问题,实际上是计算机科学中最经典的NP完全问题之一。 ...
1975年,贝尔实验室的Stephen Johnson做出了一个决定:将C编译器从手写递归下降解析器切换到DeRemer的LALR算法。这是Unix历史上第一次大规模采用自动生成的解析器。然而不到二十年,GCC项目反其道而行之,重新改用手写解析器。Clang紧随其后。直到今天,几乎所有主流编译器——GCC、Clang、V8、Lua——都使用手写递归下降解析器。为什么解析器生成器在工业界遭遇滑铁卢?这场跨越半个世纪的"代际战争"背后,隐藏着怎样的技术权衡? ...
同一个Python程序,在标准CPython解释器下运行需要47秒,换成PyPy只需要2秒。代码完全相同,没有改动任何一行,性能却提升了23倍。这不是魔法,而是即时编译器(JIT)的功劳。 ...