编译器Pass

2025年6月17日 | 阅读时长8分钟

Pass 是对源程序的一次完整遍历。 编译器 有两次遍历源程序的通道。

编译器在源代码或中间表示中进行的遍历以分析不同程序的这个过程,在编译器设计中称为通道(passes)。它通过研究不同的程序来帮助将它们转换为机器码。这个过程中有不同的 阶段,例如词法分析、语法分析、语义分析、中间代码生成、优化和最终代码生成。它们都负责各自的任务,这有助于提高准确性和效率。整个过程会增加编译器的复杂性。它取决于翻译源代码的通道次数。

Compiler Passes

多通道编译器

  • 多通道编译器用于多次处理程序的源代码。
  • 在多通道编译器中,源代码程序会经过多个阶段或通道进行处理。
  • 在此过程中,每个通道执行许多特定的任务,例如解析、语义分析、中间代码生成优化和最终代码生成

多通道编译器中的各种通道是:

第一通道 - 词法和语法分析

在第一通道中,编译器可以读取源程序,扫描它,提取标记,并将结果存储在输出文件中。第一通道包括编译器执行词法分析阶段的过程。通过分解代码行来生成标记。有不同类型的标记,例如关键字、字面量、标识符、运算符和符号。所有这些标记都用于通过语法分析器,然后该语法分析器有助于创建解析树或语法树,该树用于表示源代码的语法结构。

语法分析器还确保代码遵循编程语言的所有语法规则,使用 LL、LRSLRLALR 解析器 等算法。此过程生成包含语法树的文件或数据结构作为通道的输出。

第二通道 - 语义分析

语义分析过程在第二通道中完成,这里编译器检查语法树是否正确遵循语言的语义规则。它包括

  • 类型检查,
  • 作用域解析
  • 函数调用验证
  • 汇编等语言中的标签解析。

这个语义分析器有助于生成带注释的语法树,其中向树的节点添加了属性或不同类型。

第三通道 - 中间代码生成

在第三通道中,编译器可以读取第二个通道产生的输出文件,并检查树是否遵循语言规则。语义分析阶段的输出是带注释的语法树。

在第三通道中,带注释的语法树被转换为中间表示 (IR)。IR 作为机器代码和高级代码之间的桥梁。IR 有许多常见形式,包括 三地址码四元组抽象语法树 和静态单赋值 (SSA) 形式。

这种表示不依赖于源代码或目标语言。它还允许代码转换和优化。

第四通道 - 优化

在此通道中执行中间代码优化,以通过减少内存使用来提高其效率,而不会改变程序的任务。

在此过程中,优化包括常量折叠、死代码消除、循环展开、强度降低以及公共子表达式消除等过程。

优化可以是机器相关的(针对硬件特定)和机器无关的(针对高级语言)。

最后通道 - 代码生成和汇编

在这个最后通道中,通过翻译优化的 IR 来生成目标机器代码。代码生成器用于将 IR 指令映射到机器指令。

此通道还处理以下过程:

寄存器分配、指令选择和调度。窥孔优化、汇编和链接。

多通道编译器的优点

  1. 中间表示可以实现更好的优化
  2. 通过多通道编译器,模块化结构更易于实现和调试
  3. 它可以处理复杂的语言特性
  4. 使用多通道编译器可以改进错误报告

多通道编译器的缺点

  1. 多个通道导致编译过程变慢
  2. 额外的内存需求
  3. 复杂的实现

 

单通道编译器

  • 单通道编译器只对每个编译单元的部分进行一次遍历。它将每个部分翻译成最终的机器代码。
  • 在单通道编译器中,一次遍历源代码足以完成编译过程。它会读取代码,对其进行分析,然后一次性将程序代码逐行翻译,而无需重新访问任何前一行。
  • 然后对每一行的语法进行分析,并构建树结构。在语义部分之后,生成代码。对于每一行代码,重复相同的过程,直到整个程序编译完成。

单通道编译器中的过程

  1. 词法分析:通过扫描当前行从代码中提取标记。
  2. 语法分析:通过立即解析行来完成语法检查。
  3. 语义分析:它执行当前行的符号解析和类型检查。
  4. 代码生成:通过直接转换已解析和分析的行来生成机器代码或汇编代码。

单通道编译器的特点

  • 使用 符号表 和 **前瞻缓冲区** 管理前向引用。
  • 它并不太复杂。
  • 声明和定义被仔细管理。

单通道编译器的优点

  • 编译过程更快。
  • 内存需求更少。
  • 适用于小型程序或嵌入式系统。

单通道编译器的缺点

  • 优化有限。
  • 前向引用和复杂结构难以处理。
  • 它们不适用于现代编程语言,因为它们具有复杂的特性。

如何选择:单通道 vs. 多通道编译器

Compiler Passes

我们已经详细讨论了单通道和多通道编译器。因此,选择正确的编译器变得更加重要。两者都有其优点和缺点。为单通道和多通道选择最佳编译器策略是此开发和编译器设计阶段的一个关键决策。这个选择过程还负责影响编译器的性能、可维护性、功能、可扩展性和效率。

在考虑具体用例时,有几个因素决定了最佳适用性。

语言复杂性

基于处理 Java、C++ 等高级语言的能力,这些语言通常包含诸如

  • 前向声明
  • 函数重载
  • 继承和多态
  • 模板或泛型编程
  • 复杂的词法和类型系统

代码程序中后面会跟许多声明和定义,以及对源代码的多次遍历是这些特性所必需的。例如,在 C++ 等语言中,函数在使用之前就已经被定义了。在这种前向引用中,需要存储大量部分信息,这在单通道编译器中是不可能的。

而多通道编译器非常有用,并且能够处理这些复杂性。另一方面,单通道编译器最适合更简单的语言,例如过程式语言(Pascal 或 Fortran);在这里,强制执行从上到下的严格顺序。

资源限制

单通道编译器轻量级且高效的特性使其最适合物联网 (IoT) 设备、微控制器和嵌入式系统;这些是资源受限的环境,例如

优点

  1. 内存利用率高,因为它使用的内存少,因为编译器不存储中间表示或符号表。
  2. 提高效率,因为它减少了编译时间,通过减少多次文件读取的开销使其更快,并且编译器只处理代码一次。
  3. 它具有非常简单的架构,具有最少的编译器阶段,并且复杂度较低,二进制文件大小也较小。

例如,单通道编译器允许开发人员快速测试并将代码直接部署到硬件,因为它嵌入在微控制器中。

但这也会影响代码优化,使其受限,并且仅支持有限的语言,这对于复杂的应用程序和关键性能来说非常必要。

性能要求和优化需求

这对于提高性能敏感的应用程序非常重要,尤其是在以下领域:

  • 科学计算
  • 游戏开发
  • 机器学习
  • 高频交易

它在最终编译程序的运行时性能中起着非常重要的作用。对于这些场景,多通道编译器是最佳选择,因为它们具有以下能力:

  1. 它们执行深度优化。在此过程中有许多中间表示和分析。编译器可以优化循环,还可以帮助改善内存使用和指令选择,并通过改进控制流来直接控制。
  2. 此多通道编译器还通过抽象中间代码来定位多个体系结构。在这里,一个前端支持多个后端。例如,ARM 或 WebAssembly。
  3. 它有助于生成高效的机器代码。有优化高级技术,如全局分析、寄存器分配和指令调度。

多通道编译器会增加额外的编译时间;那时,为提高运行时效率而进行的权衡对于高复杂性能来说是非常有利的。

编译器开发和维护的简易性

编译器的构建是一项非常复杂的软件工程任务。因此,对于这项复杂的任务,在开发过程中通常首选多通道编译器。原因如下:

模块化:有不同的任务,如解析、语义分析、代码生成,为这些任务指定了特定的通道,该通道负责该特定任务。

改进错误诊断:当发生错误时,可以在特定时间捕获它;这有助于开发人员和最终用户妥善处理和解决问题。

可扩展性:无需影响整个编译器管道即可添加新的优化技术和语言支持。

改进的调试和测试:通过包含分配给语法树、IR 等的每个特定通道的中间输出来改进测试技术,并且可以独立进行测试。

常见问题解答 (FAQs)

Q1:为什么大多数现代编译器都采用多通道设计?

答:C++、Rust 等现代语言需要进行深度优化和错误检查分析,这只有通过多通道才能实现。

Q2:单通道编译器是否支持在之前声明函数?

答:否,单通道编译器由于按顺序处理代码,因此难以处理前向引用。

Q3:Java 是多通道编译器吗?

答:是的,javac(Java 编译器)使用多个通道进行字节码生成和优化。

Q4:解释器是单通道还是多通道?

答:大多数解释器使用单通道执行,但有些(如 Python 的字节码解释器)具有多阶段处理。

Q5:编译器是否可以在单通道和多通道模式之间切换?

答:某些编译器(如 Turbo Pascal)允许选择模式,但大多数编译器都是为一种方法设计的。


下一主题自举