编译器被组织成了多次遍历。在编译器获得转换代码知识的同时,必须记录下这些知识然后传递给后面的遍历。因此编译器需要表征所有从程序中获得的知识。我们称这些数据结构的集合为 intermediate representation(IR)。编译可能有一种 IR,也可能有一系列 IRs 用来将源码转换为目标语言。编译器依赖 IR 表征程序,不会返回参考源码的文本。IR 的属性对编译器有直接影响,但是不对代码有影响。

使用 IR 让编译器可以多次遍历代码。编译器如果可以在一次遍历中获取信息然后在下一次遍历中使用就可以为输入程序生成更高效率的代码。但是这种能力需要:IR 必须可以表征获取的信息。因此编译器必须建立一些辅助数据结构表示获取的信息。这些结构也是 IR 的一部分。

编译器几乎每个阶段都以 IR 形式操作程序。因此 IR 的属性,比如读写特定字段的方法来得到特定事实,以及导航整个程序,对于编写单独的遍历是否容易,以及一个阶段的执行是否高效有着直接的影响。

Conceptual Roadmap

本章集中于编译过程中 IR 的设计和使用。有些编译器使用树和图表示程序。比如,解析树简单地捕获了解析器建立的知识,以及 Lisp 的 S-expression 就是简单的图。因为大多数处理器依赖线性的汇编语言,编译器经常使用 linear IR 表示汇编代码。这样的 linear IR 可以暴露目标机器代码的属性给编译器。

在编译器建立程序的 IR 形式同时,也发现和获取不能简单增加到树/图或者 linear IR 的信息。编译器必须理解程序的命名空间然后建立辅助结构记录获取到的信息。编译器必须创建存储布局的计划使得编译后的代码可以将数据存储到内存以及读取。最后,还需要通过名字高效访问所有获取到的信息。为了满足这些需要,编译器建立一系列辅助结构(与树/图/linear IR 同时存在)并且发挥着同样关键的作用。

Overview

现代多次遍历的编译器使用 IR 来建模被分析,转化,优化的代码。大多数的遍历读取的是 IR;Lexer 产生的单词流可以被视为 lexer 和 parser 之间通信的 IR。大多数遍历也会产生 IR;代码生成阶段是个例外。很多现代编译器使用多种 IR 在一次编译过程中。在 pass-structured 编译器中,IR 就是代码的主要表示。

编译器的 IR 必须有足够的表达能力来记录所有编译器需要的信息。对于这个目的而言,源码是不足的;编译器需要获取很多在源码中没有表示的事实。比如变量的地址,给定参数传递使用的寄存器数量。为了记录所有编译器必须编码的细节,大多数编译器开发者通过表和集合记录额外信息增加 IR。这些结构也是 IR 的一部分。

选择合适的 IR 需要对源码,目标机器语言,编译器目标,以及应用的属性有足够的了解。比如源码-源码转换器必须使用解析树来表示源码,为微控制器生成汇编代码的编译器必须使用底层 IR。类似的,C 的编译器可能需要指针值的标注。Java 或者 C++ 的编译器需要记录类继承关系的事实。

实现 IR 需要编译器开发者关注实践问题。IR 是编译器的核心结构。编译器需要高效执行经常执行的操作。需要精确的方式表达编译过程中需要的所有结构。最后,开发者需要一种可以让人类轻松检查 IR 的机制。【为了开发过程的愉快,一定要保证最后一点】

本章剩余部分探究了设计与使用 IR 的问题。4.2 提供了 IR 的分类以及它们的属性。4.3 描述了几种基于树和图的 IR,4.4 描述了几种线性 IR。4.5 提供了高层次的 symbol table 的概览以及他们的使用。4.64.7 探究了编译器名字值以及编译器将值放置到内存规则的问题。

A few words about time

IR 完全是编译期结构。因此编译器开发者完全控制 IR 的设计,在设计期。有些辅助信息比如 symbol tables ,storage map 也会被后续的工具使用,比如 debugger。但是不影响 IR 的设计与实现,因为这些信息必须转换为后续工具需要的格式