编译器中间表示的设计哲学:从单层IR到多层次抽象的六十年演进

1957年,IBM的John Backus团队发布了第一个Fortran编译器。这个改变计算机科学历史的程序有一个鲜为人知的细节:它没有使用任何我们今天称为"中间表示"的东西。编译器直接从源代码生成机器码,所有优化都在生成过程中即时完成。这种方式在今天看来几乎不可想象,但它恰恰揭示了编译器设计中一个根本性的问题——为什么我们需要中间表示? ...

17 min · 8138 words

泛型的三种命运:为什么同一个概念在不同语言中走向了截然不同的实现道路

一个概念,三种命运 1975年,Robin Milner在ML语言中首次引入了参数化多态(parametric polymorphism)——这便是我们今天所称"泛型"的理论源头。半个世纪过去,这个概念已经渗透进几乎所有主流编程语言。然而,当你打开C++、Java、Go、Rust、C#的编译器源码,会发现同一个"泛型"概念在这些语言中竟然演化出了截然不同的实现策略。 ...

16 min · 7926 words

编译器寄存器分配:从图着色到线性扫描的四十年算法博弈

1981年,IBM的研究员Gregory Chaitin面临一个棘手的问题:如何让PL.8编译器生成的代码更高效?当时,程序中的变量远多于处理器寄存器,编译器必须决定哪些变量驻留在寄存器,哪些被"驱逐"到内存。这个看似简单的资源分配问题,实际上是计算机科学中最经典的NP完全问题之一。 ...

10 min · 4809 words
Blog Cover

解析器的代际战争:为什么GCC和Clang都选择了手写解析器

1975年,贝尔实验室的Stephen Johnson做出了一个决定:将C编译器从手写递归下降解析器切换到DeRemer的LALR算法。这是Unix历史上第一次大规模采用自动生成的解析器。然而不到二十年,GCC项目反其道而行之,重新改用手写解析器。Clang紧随其后。直到今天,几乎所有主流编译器——GCC、Clang、V8、Lua——都使用手写递归下降解析器。为什么解析器生成器在工业界遭遇滑铁卢?这场跨越半个世纪的"代际战争"背后,隐藏着怎样的技术权衡? ...

16 min · 7517 words

编译器的循环优化技术:从循环展开到分块,为何几行代码的改动能让程序快十倍

计算机科学领域存在一个广为流传的二八定律:程序80%的执行时间耗费在20%的代码上,而这20%的代码中,循环结构占据主导地位。正因如此,循环优化成为编译器技术中最核心的研究方向之一。 ...

7 min · 3403 words

JIT编译如何让解释型语言跑出编译型语言的速度?

同一个Python程序,在标准CPython解释器下运行需要47秒,换成PyPy只需要2秒。代码完全相同,没有改动任何一行,性能却提升了23倍。这不是魔法,而是即时编译器(JIT)的功劳。 ...

12 min · 5962 words

类型推断的错误消息为何如此难懂:从统一算法到错误定位的四十年迷局

一位TypeScript开发者在重构代码时遇到了这样一个错误: Type 'string' is not assignable to type 'number'. 错误指向了一个看似完全正确的函数调用。他检查了函数签名,检查了传入的参数类型,甚至检查了调用链上的每一个函数,花了整整两个小时才找到问题的根源——一个在文件另一端、三百行之外的对象属性赋值。 ...

9 min · 4270 words