编译原理实战:如何用DAG优化你的中间代码生成(附Python示例)

编译原理实战:如何用DAG优化你的中间代码生成(附Python示例)

很多朋友啃完了编译原理的经典教材,对词法分析、语法分析、语义分析这些前端流程了然于胸,甚至能手动推导出三地址码。但一到自己动手写代码,想把学到的优化理论落地时,就卡壳了。理论书上说,有向无环图(DAG)能消除公共子表达式,能优化常量传播,听起来很美,可具体到代码层面,这个“图”到底怎么构建?节点怎么设计?优化后的指令又该如何生成?这些细节往往一笔带过,留下满脑子问号的开发者。

这篇文章就是来解决这个“最后一公里”问题的。我们不打算重复教科书上的形式化定义,而是直接切入一个编译器后端开发者的视角,用Python代码一步步搭建一个简易的DAG中间表示,并演示如何利用它完成一次实实在在的公共子表达式消除。你会发现,当抽象的理论变成屏幕上可运行、可调试的代码时,那些原本模糊的概念会瞬间清晰起来。

1. 从抽象语法树到DAG:思维模式的转变

在编译器前端,我们习惯用抽象语法树(AST)来表示程序结构。AST忠实地反映了源代码的语法嵌套关系,一个表达式 a + b * c 在AST中,+ 节点是根,左孩子是 a,右孩子是一个 * 节点,而 * 节点又有自己的左右孩子 bc。这种结构清晰,但存在冗余。考虑表达式 (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(或引用)组合成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值