编译原理实战:如何用DAG优化你的中间代码生成(附Python示例)
很多朋友啃完了编译原理的经典教材,对词法分析、语法分析、语义分析这些前端流程了然于胸,甚至能手动推导出三地址码。但一到自己动手写代码,想把学到的优化理论落地时,就卡壳了。理论书上说,有向无环图(DAG)能消除公共子表达式,能优化常量传播,听起来很美,可具体到代码层面,这个“图”到底怎么构建?节点怎么设计?优化后的指令又该如何生成?这些细节往往一笔带过,留下满脑子问号的开发者。
这篇文章就是来解决这个“最后一公里”问题的。我们不打算重复教科书上的形式化定义,而是直接切入一个编译器后端开发者的视角,用Python代码一步步搭建一个简易的DAG中间表示,并演示如何利用它完成一次实实在在的公共子表达式消除。你会发现,当抽象的理论变成屏幕上可运行、可调试的代码时,那些原本模糊的概念会瞬间清晰起来。
1. 从抽象语法树到DAG:思维模式的转变
在编译器前端,我们习惯用抽象语法树(AST)来表示程序结构。AST忠实地反映了源代码的语法嵌套关系,一个表达式 a + b * c 在AST中,+ 节点是根,左孩子是 a,右孩子是一个 * 节点,而 * 节点又有自己的左右孩子 b 和 c。这种结构清晰,但存在冗余。考虑表达式 (a+b) * (a+b),在AST中,(a+b) 这个子表达式会完整地出现两次,生成两棵独立的子树。
DAG的核心思想就是打破这种“树”的思维,引入“图”的共享。在DAG中,每一个值(无论是变量、常量还是计算出的中间结果)都对应一个唯一的节点。如果同一个值在程序中被多次计算,那么所有这些计算点都指向同一个DAG节点。这种表示法天然地蕴含了公共子表达式信息。
1.1 DAG节点的设计哲学
一个实用的DAG节点需要包含哪些信息?我们从一个简单的算术表达式语言入手,它只包含变量、整数常量和 +、-、* 这些二元操作。
class DAGNode:
__slots__ = ('node_id', 'op', 'left', 'right', 'value', 'var_name')
_id_counter = 0
def __init__(self, op=None, left=None, right=None, value=None, var_name=None):
self.node_id = DAGNode._id_counter
DAGNode._id_counter += 1
self.op = op # 操作符,如 '+', '-', '*', 或 'const', 'var'
self.left = left # 左子节点(ID或对象引用)
self.right = right # 右子节点(ID或对象引用)
self.value = value # 常量值(如果op是'const')
self.var_name = var_name # 变量名(如果op是'var')
def __repr__(self):
if self.op == 'const':
return f'Node({self.node_id}: const {self.value})'
elif self.op == 'var':
return f'Node({self.node_id}: var {self.var_name})'
else:
return f'Node({self.node_id}: {self.op} [{self.left}, {self.right}])'
这里有几个关键点:
- 唯一标识符 (
node_id):每个节点有一个唯一的ID,方便在哈希表等数据结构中快速查找和比较。 - 操作类型 (
op):区分节点是叶子节点(常量、变量)还是内部节点(运算符)。 - 子节点引用 (
left,right):对于内部节点,指向其操作数节点。这里可以存储节点ID,也可以直接存储节点对象的引用。为了简化,后续示例我们使用引用。 - 值/名称:叶子节点存储具体信息。
注意:在实际工业级编译器中,DAG节点的设计会复杂得多,可能包含类型信息、副作用标记、内存访问模式等。我们这里做最大程度的简化,聚焦于核心概念。
1.2 构建DAG:查表与创建
构建DAG的过程,本质上是一个规范化或哈希合并的过程。我们维护一个字典(哈希表),其键是节点的“特征”,值是已存在的节点对象。当需要创建一个新节点时,先根据其特征在字典中查找;如果找到,则直接返回已有节点;否则,才创建新节点并加入字典。
这个“特征”对于常量节点就是其值,对于变量节点就是其名称,对于运算符节点则是其操作符和左右子节点的ID(或引用)组合成

&spm=1001.2101.3001.5002&articleId=154109402&d=1&t=3&u=6b5e749b0b554a70851b7d618efe9e5d)
137

被折叠的 条评论
为什么被折叠?



