linearize:控制流图(CFG)转换为线性指令序列

概述

linearize 函数将控制流图(CFG)转换为线性指令序列,这是代码生成的最后一步。

它通过深度优先遍历基本块,正确处理分支和跳转,确保每个基本块只出现一次,并生成可直接汇编的顺序指令列表。

代码

rust 复制代码
/// `linearize` 函数:将控制流图线性化为指令序列
///
/// 返回值:Vec<RV64Operation> - 线性化的RISC-V指令序列
///
/// 功能:执行深度优先的图遍历,将控制流图转换为线性指令序列
/// 这是代码生成的最后一步,生成可以直接汇编的指令序列
pub fn linearize(self) -> Vec<RV64Operation> {
    // 步骤1:初始化数据结构
    // placed: 记录已经放置到线性序列中的基本块标签
    let mut placed = HashSet::new();

    // worklist: 工作队列,用于深度优先遍历控制流图
    // 使用VecDeque作为双端队列,支持前端和后端操作
    let mut worklist = VecDeque::new();

    // 将入口块添加到工作队列
    worklist.push_back(self.get_block(self.get_entry()));

    // result: 存储最终的线性指令序列
    let mut result = Vec::new();

    // 步骤2:主循环 - 深度优先遍历控制流图
    // 使用while let模式匹配,从工作队列中取出下一个要处理的基本块
    while let Some(to_place) = worklist.pop_front() {
        // 步骤2.1:检查基本块是否已经处理过
        // 避免重复处理同一个基本块(循环控制流)
        if placed.contains(&to_place.label) {
            continue;  // 已处理,跳过
        }

        // 步骤2.2:为当前基本块插入标签指令
        // PSEUD_LABEL是伪指令,表示基本块的标签
        result.push(RV64Operation::PSEUD_LABEL(Rc::clone(&to_place.label)));

        // 步骤2.3:遍历当前基本块中的所有指令
        for op in to_place.body.iter() {
            // 根据指令类型进行不同的处理
            match op {
                // 情况1:处理无条件跳转指令 PSEUD_J
                // 格式:PSEUD_J(target_label)
                RV64Operation::PSEUD_J(t) => {
                    // 检查目标标签是否已经放置
                    if placed.contains(t) {
                        // 目标已放置:直接添加跳转指令
                        result.push(op.clone());
                    } else {
                        // 目标未放置:将目标块添加到工作队列前端
                        // 使用前端添加实现深度优先遍历
                        worklist.push_front(
                            self.blocks.iter().find(|b| &b.label == t).unwrap(),
                        );
                    }
                }
                // 情况2:处理条件分支指令
                // 包括:BEQ(相等分支)、BGE(大于等于分支)、BL(小于分支)
                // 格式:BEQ/BGE/BL(reg1, reg2, target_label)
                RV64Operation::BEQ(_, _, l)
                | RV64Operation::BGE(_, _, l)
                | RV64Operation::BL(_, _, l) => {
                    // 断言:条件分支指令必须有两个后继
                    debug_assert_eq!(to_place.children.len(), 2);

                    // 获取false分支(第二个后继)
                    let r = self.get_block(to_place.children[1]);

                    // 添加条件分支指令到结果序列
                    result.push(op.clone());

                    // 检查false分支是否已经放置
                    if placed.contains(&r.label) {
                        // false分支已放置:添加无条件跳转到false分支
                        result.push(RV64Operation::PSEUD_J(Rc::clone(&r.label)));
                    } else {
                        // false分支未放置:将false分支添加到工作队列前端
                        worklist.push_front(r);
                    }

                    // 将true分支(目标标签)添加到工作队列后端
                    // 使用后端添加,确保true分支在false分支之后处理
                    worklist.push_back(self.blocks.iter().find(|b| &b.label == l).unwrap());
                }

                // 情况3:处理其他所有指令
                // 包括:算术指令、内存访问指令、伪指令等
                op => {
                    // 直接添加到结果序列
                    result.push(op.clone());
                }
            }
        }

        // 步骤2.4:标记当前基本块为已处理
        placed.insert(Rc::clone(&to_place.label));
    }

    // 步骤3:返回线性化的指令序列
    result
}

主要作用

将图状结构线性化 :CFG 中的基本块通过边(分支/跳转)连接,无法直接输出为指令流。linearize 通过遍历将这些块按深度优先顺序排列,使生成的指令序列在逻辑上等价于原控制流。

插入标签 :为每个基本块生成一个标签(PSEUD_LABEL),作为跳转目标

处理跳转

  • 对于无条件跳转(PSEUD_J):如果目标块尚未放置,则将其加入工作队列前端(深度优先);如果已放置,则直接生成跳转指令。
  • 对于条件分支(BEQ/BGE/BL):先输出分支指令,然后将 false 分支(fall-through 路径)加入队列前端(优先处理),将 true 分支(显式跳转目标)加入队列后端(稍后处理),并在 false 分支已放置时插入一个显式的无条件跳转以维持控制流。

保证单次访问 :使用 placed 集合记录已处理的基本块,避免重复处理(例如循环)。

举例说明

假设有以下简单控制流图(用伪代码表示):

text 复制代码
BB0:
  inst0
  beq r1, r2, BB2   ; 如果 r1 == r2,跳转到 BB2,否则执行 BB1

BB1:
  inst1
  j BB3

BB2:
  inst2
  j BB3

BB3:
  inst3
  ret

执行过程

  1. 初始化:工作队列 worklist 包含入口块 BB0,placed 为空。
  2. 处理 BB0
    • 输出 PSEUD_LABEL BB0。
    • 处理 inst0,直接输出。
    • 遇到条件分支 beq r1, r2, BB2:
      • 输出该分支指令。
      • false 分支为 BB1(children[1]),未放置 → 将 BB1 加入队列前端。
      • true 分支 BB2 未放置 → 将 BB2 加入队列后端。
      • 标记 BB0 为已放置。此时工作队列:[BB1, BB2]。
    • 标记 BB0 为已放置。
      • 此时工作队列:[BB1, BB2]。
  3. 处理 BB1 (从队列前端取出):
    • 输出 PSEUD_LABEL BB1。
    • 处理 inst1,直接输出。
    • 遇到无条件跳转 j BB3:
      • 检查 BB3 是否已放置(尚未)→ 将 BB3 加入队列前端。
      • 标记 BB1 已放置。
        • 工作队列:[BB3, BB2]。
  4. 处理 BB3
    • 输出 PSEUD_LABEL BB3。
    • 处理 inst3 和 ret,直接输出。
    • 标记 BB3 已放置。工作队列:[BB2]。
  5. 处理 BB2
    • 输出 PSEUD_LABEL BB2。
    • 处理 inst2,直接输出。
    • 遇到无条件跳转 j BB3:
      • BB3 已放置 → 直接输出该跳转指令。
    • 标记 BB2 已放置。工作队列为空,结束。

最终线性指令序列

text 复制代码
PSEUD_LABEL BB0
inst0
beq r1, r2, BB2
PSEUD_LABEL BB1
inst1
j BB3
PSEUD_LABEL BB3
inst3
ret
PSEUD_LABEL BB2
inst2
j BB3

该序列保持了与原控制流图相同的语义:

  • BB0 开始,根据条件分支进入 BB1BB2
  • BB1BB2 最终都跳转到 BB3 执行 ret

为什么使用深度优先策略?

局部性:深度优先遍历倾向于将基本块按执行路径紧密排列,有利于指令缓存。

简单性:利用双端队列,可以自然地实现分支路径的优先处理(将 fall-through 路径优先输出,减少跳转)。

该函数是编译器后端从中间表示到最终汇编输出的关键桥梁,确保生成的机器码可以正确执行。

相关推荐
一直都在5722 小时前
Java线程池
java·开发语言
2401_873204652 小时前
基于C++的区块链实现
开发语言·c++·算法
Frostnova丶2 小时前
LeetCode 3546. 等和矩阵分割
算法·leetcode·矩阵
智算菩萨2 小时前
OpenCV几何图形绘制工具全栈开发:从中文路径支持到交互式GUI的完整实战(附源码)
开发语言·图像处理·人工智能·python·opencv·计算机视觉
掘金者阿豪2 小时前
从聊天入口到系统治理:深度解读“小龙虾 Web / OpenClaw”左侧导航的产品设计逻辑
后端
ShineWinsu2 小时前
对于Linux:基础开发工具(vim、gcc/g++)的介绍
linux·运维·服务器·c++·面试·编辑器·vim
二闹2 小时前
变量世界的“通行证”:理解Python中的global与nonlocal
后端·python
亚马逊云开发者2 小时前
article
java·开发语言
集智飞行2 小时前
安装rust和cargo
开发语言·后端·rust