回答您的问题的最好的书可能是:Cooper和Torczon,“工程编译器”,2003年。如果您可以访问大学图书馆,则应该可以借阅一本。
在像llvm或gcc这样的生产编译器中,设计人员会尽一切努力使所有算法保持在,其中是输入的大小。对于“优化”阶段的某些分析,这意味着您需要使用启发式方法,而不是生成真正的最佳代码。nO(n2)n
词法分析器是一个有限状态机,因此输入大小为(以字符为单位),并生成令牌流,该令牌流将传递给解析器。O (n )O(n)O(n)
对于使用多种语言的许多编译器,解析器为LALR(1),因此在输入令牌数量的时间中处理令牌流。在解析期间,您通常必须跟踪符号表,但是对于许多语言而言,可以使用一堆哈希表(“字典”)来处理。每个字典访问都是,但是您有时可能不得不走栈来查找符号。堆栈的深度为,其中为合并范围的嵌套深度。(因此,在类似C的语言中,您内部有多少层花括号。)O (1 )O (s )秒O(n)O(1)O(s)s
然后,通常将解析树“展平”为控制流程图。控制流程图的节点可能是3地址指令(类似于RISC汇编语言),并且控制流程图的大小通常在解析树的大小上是线性的。
然后通常采用一系列冗余消除步骤(常见子表达式消除,循环不变代码运动,恒定传播等)。(这通常被称为“优化”,尽管结果几乎没有什么优化,真正的目标是在我们对编译器施加的时间和空间限制内尽可能地提高代码。)每个冗余消除步骤将通常需要证明有关控制流程图的某些事实。这些证明通常使用数据流分析来完成。大多数数据流分析的设计目的是使它们收敛于流程图上的传递,其中是(大致而言)循环嵌套深度,而流程图上的传递花费时间d O (n )nO(d)dO(n)其中是3地址指令的数量。n
对于更复杂的优化,您可能需要进行更复杂的分析。此时,您开始遇到权衡。您希望您的分析算法所花费的时间少于O(n2)时间花费在整个程序流程图的大小上,但这意味着您需要在没有可能证明昂贵的信息(程序改进转换)的情况下进行操作。别名分析就是一个典型的例子,对于某些存储器写对,您想证明这两个写永远不能针对相同的存储器位置。(您可能要进行别名分析,以查看是否可以将一条指令移到另一条指令上。)但是,要获取有关别名的准确信息,您可能需要分析程序中所有可能的控制路径,这与分支数成指数关系在程序中(因此控制流图中的节点数呈指数形式。)
接下来,进入寄存器分配。寄存器分配可以表述为图着色问题,并且用最少数量的颜色为图着色是众所周知的NP-Hard。因此,大多数编译器将某种贪婪启发式方法与寄存器溢出结合使用,目的是在合理的时间范围内尽可能减少寄存器溢出的次数。
最后,您开始进行代码生成。通常,在基本块是一组具有单个入口和单个出口的线性连接的控制流程图节点的时间,代码生成通常在最大的基本块中进行。可以将其重新表示为一个图形覆盖问题,其中您要覆盖的图形是基本块中这组3地址指令的依赖图,并且您试图覆盖代表可用机器的一组图形说明。这个问题在最大的基本块的大小上是指数级的(原则上,它可以与整个程序的大小相同),因此通常使用启发式方法来完成此任务,在这种方法中,可能覆盖的子集很小检查。