Files
mysysy/Pass_ID_List.md

9.5 KiB
Raw Blame History

记录中端遍的开发进度

名称 优化级别 开发进度
CFG优化 函数级 已完成
DCE 函数级 待正确性测试
Mem2Reg 函数级 待正确性测试
Reg2Mem 函数级 待正确性测试

部分优化遍的说明

Mem2Reg

Mem2Reg 遍的主要目标是将那些不必要的、只用于局部标量变量的内存分配 (alloca 指令) 消除,并将这些变量的值转换为 SSA 形式。这有助于减少内存访问,提高代码效率,并为后续的优化创造更好的条件。

Reg2Mem

我们的Reg2Mem 遍的主要目标是作为 Mem2Reg 的一种逆操作,但更具体是解决后端无法识别 PhiInst 指令的问题。主要的速录是将函数参数和 PhiInst 指令的结果从 SSA 形式转换回内存形式,通过插入 alloca、load 和 store 指令来实现。其他非 Phi 的指令结果将保持 SSA 形式。

SCCP

SCCP稀疏条件常量传播是一种编译器优化技术它结合了常量传播和死代码消除。其核心思想是在程序执行过程中尝试识别并替换那些在编译时就能确定其值的变量常量同时移除那些永远不会被执行到的代码块不可达代码

以下是 SCCP 的实现思路:

  1. 核心数据结构与工作列表:

Lattice 值Lattice Value: SCCP 使用三值格Three-Valued Lattice来表示变量的状态

Top (T): 初始状态,表示变量的值未知,但可能是一个常量。

Constant (C): 表示变量的值已经确定为一个具体的常量。

Bottom (⊥): 表示变量的值不确定或不是一个常量(例如,它可能在运行时有多个不同的值,或者从内存中加载)。一旦变量状态变为 Bottom它就不能再变回 Constant 或 Top。

SSAPValue: 封装了 Lattice 值和常量具体值(如果状态是 Constant

valState (map<Value, SSAPValue>):* 存储程序中每个 Value变量、指令结果等的当前 SCCP Lattice 状态。

ExecutableBlocks (set):* 存储在分析过程中被确定为可执行的基本块。

工作列表 (Worklists):

cfgWorkList (queue<pair<BasicBlock, BasicBlock>>):** 存储待处理的控制流图CFG边。当一个块被标记为可执行时它的后继边会被添加到这个列表。

ssaWorkList (queue):* 存储待处理的 SSA (Static Single Assignment) 指令。当一个指令的任何操作数的状态发生变化时,该指令就会被添加到这个列表,需要重新评估。

  1. 初始化:

所有 Value 的状态都被初始化为 Top。

所有基本块都被初始化为不可执行。

函数的入口基本块被标记为可执行,并且该块中的所有指令被添加到 ssaWorkList。

  1. 迭代过程 (Fixed-Point Iteration)

SCCP 的核心是一个迭代过程,它交替处理 CFG 工作列表和 SSA 工作列表,直到达到一个不动点(即没有更多的状态变化)。

处理 cfgWorkList:

从 cfgWorkList 中取出一个边 (prev, next)。

如果 next 块之前是不可执行的,现在通过 prev 块可达,则将其标记为可执行 (markBlockExecutable)。

一旦 next 块变为可执行,其内部的所有指令(特别是 Phi 指令)都需要被重新评估,因此将它们添加到 ssaWorkList。

处理 ssaWorkList:

从 ssaWorkList 中取出一个指令 inst。

重要: 只有当 inst 所在的块是可执行的,才处理该指令。不可执行块中的指令不参与常量传播。

计算新的 Lattice 值 (computeLatticeValue): 根据指令类型和其操作数的当前 Lattice 状态,计算 inst 的新的 Lattice 状态。

常量折叠: 如果所有操作数都是常量,则可以直接执行运算并得到一个新的常量结果。

Bottom 传播: 如果任何操作数是 Bottom或者运算规则导致不确定例如除以零则结果为 Bottom。

Phi 指令的特殊处理: Phi 指令的值取决于其所有可执行的前驱块传入的值。

如果所有可执行前驱都提供了相同的常量 C则 Phi 结果为 C。

如果有任何可执行前驱提供了 Bottom或者不同的可执行前驱提供了不同的常量则 Phi 结果为 Bottom。

如果所有可执行前驱都提供了 Top则 Phi 结果仍为 Top。

更新状态: 如果 inst 的新计算出的 Lattice 值与它当前存储的值不同,则更新 valState[inst]。

传播变化: 如果 inst 的状态发生变化,那么所有使用 inst 作为操作数的指令都可能受到影响,需要重新评估。因此,将 inst 的所有使用者添加到 ssaWorkList。

处理终结符指令 (BranchInst, ReturnInst):

对于条件分支 BranchInst如果其条件操作数变为常量

如果条件为真,则只有真分支的目标块是可达的,将该边添加到 cfgWorkList。

如果条件为假,则只有假分支的目标块是可达的,将该边添加到 cfgWorkList。

如果条件不是常量Top 或 Bottom则两个分支都可能被执行将两边的边都添加到 cfgWorkList。

这会影响 CFG 的可达性分析,可能导致新的块被标记为可执行。

  1. 应用优化 (Transformation)

当两个工作列表都为空,达到不动点后,程序代码开始进行实际的修改:

常量替换:

遍历所有指令。如果指令的 valState 为 Constant则用相应的 ConstantValue 替换该指令的所有用途 (replaceAllUsesWith)。

将该指令标记为待删除。

对于指令的操作数,如果其 valState 为 Constant则直接将操作数替换为对应的 ConstantValue常量折叠

删除死指令: 遍历所有标记为待删除的指令,并从其父基本块中删除它们。

删除不可达基本块: 遍历函数中的所有基本块。如果一个基本块没有被标记为可执行 (ExecutableBlocks 中不存在),则将其从函数中删除。但入口块不能删除。

简化分支指令:

遍历所有可执行的基本块的终结符指令。

对于条件分支 BranchInst如果其条件操作数在 valState 中是 Constant

如果条件为真,则将该条件分支替换为一个无条件跳转到真分支目标块的指令。

如果条件为假,则将该条件分支替换为一个无条件跳转到假分支目标块的指令。

更新 CFG移除不可达的分支边和其前驱信息。

computeLatticeValue 的具体逻辑:

这个函数是 SCCP 的核心逻辑,它定义了如何根据指令类型和操作数的当前 Lattice 状态来计算指令结果的 Lattice 状态。

二元运算 (Add, Sub, Mul, Div, Rem, ICmp, And, Or):

如果任何一个操作数是 Bottom结果就是 Bottom。

如果任何一个操作数是 Top结果就是 Top。

如果两个操作数都是 Constant执行实际的常量运算结果是一个新的 Constant。

一元运算 (Neg, Not):

如果操作数是 Bottom结果就是 Bottom。

如果操作数是 Top结果就是 Top。

如果操作数是 Constant执行实际的常量运算结果是一个新的 Constant。

Load 指令: 通常情况下Load 的结果会被标记为 Bottom因为内存内容通常在编译时无法确定。但如果加载的是已知的全局常量可能可以确定。在提供的代码中它通常返回 Bottom。

Store 指令: Store 不产生值,所以其 SSAPValue 保持 Top 或不关心。

Call 指令: 大多数 Call 指令(尤其是对外部或有副作用的函数)的结果都是 Bottom。对于纯函数如果所有参数都是常量理论上可以折叠但这需要额外的分析。

GetElementPtr (GEP) 指令: GEP 计算内存地址。如果所有索引都是常量,地址本身是常量。但 SCCP 关注的是数据值,因此这里通常返回 Bottom除非有特定的指针常量跟踪。

Phi 指令: 如上所述,基于所有可执行前驱的传入值进行聚合。

Alloc 指令: Alloc 分配内存,返回一个指针。其内容通常是 Bottom。

Branch 和 Return 指令: 这些是终结符指令,不产生一个可用于其他指令的值,通常 SSAPValue 保持 Top 或不关心。

类型转换 (ZExt, SExt, Trunc, FtoI, ItoF): 如果操作数是 Constant则执行相应的类型转换结果仍为 Constant。对于浮点数转换由于 SSAPValue 的 constantVal 为 int 类型,所以对浮点数的操作会保守地返回 Bottom。

未处理的指令: 默认情况下,任何未明确处理的指令都被保守地假定为产生 Bottom 值。

浮点数处理的注意事项:

在提供的代码中SSAPValue 的 constantVal 是 int 类型。这使得浮点数常量传播变得复杂。对于浮点数相关的指令kFAdd, kFMul, kFCmp, kFNeg, kFNot, kItoF, kFtoI 等),如果不能将浮点值准确地存储在 int 中,或者不能可靠地执行浮点运算,那么通常会保守地将结果设置为 Bottom。一个更完善的 SCCP 实现会使用 std::variant<int, float> 或独立的浮点常量存储来处理浮点数。

后续优化可能涉及的改动

1将所有的alloca集中到entryblock中

好处优化友好性方便mem2reg提升 目前没有实现这个机制,如果想要实现首先解决同一函数不同域的同名变量命名区分 需要保证符号表能正确维护域中的局部变量

关于中端优化提升编译器性能的TODO

usedelete_withinstdelte方法

这个方法删除了use关系并移除了指令逻辑是根据Instruction* inst去find对应的迭代器并erase 有些情况下外部持有迭代器和inst,可以省略find过程