后端Pass简介——IndirectBrExpandPass
IndirectBrExpandPass
此 Pass 将近 300 行。
这个 Pass主要用来将 LLVM IR 中的 ==indirectbr 指令展开成等价的 switch 指令==,方便那些目标架构不直接支持间接跳转(indirect branch)或希望通过内建的 switch lowering 来生成代码时使用。
我们知道,indirectbr 用于 LLVM IR 中的间接跳转,目标不是固定的标签,而是一个运行时计算得到的地址。
那么可以看一下如下的例子,假设有这样一段伪 IR:
; 原始 IR:两个基本块 A 和 B,各自都有一个 indirectbr
entry:
%addr = ; 某种方式得到要跳转到 A 或 B 的地址
indirectbr %addr, [label %A, label %B]
foo:
; 又一处间接跳转
%addr2 = …
indirectbr %addr2, [label %A, label %B]
经过 IndirectBrExpandPass 处理后,大致会变成:
; 为 A 分配编号 1,为 B 分配编号 2
; 所有 blockaddress 都替换成 inttoptr(1) 或 inttoptr(2)
entry:
%cast₁ = ptrtoint %addr to iN ; N 是目标机器指针宽度
br label %switch_bb
foo:
%cast₂ = ptrtoint %addr2 to iN
br label %switch_bb
switch_bb:
; 汇聚不同来源的 cast 值
%phi = phi iN [%cast₁, %entry], [%cast₂, %foo]
switch iN %phi, label %unreachable [
iN 1, label %A
iN 2, label %B
]
这样就把所有的间接跳转都归一为一个 switch,后端就能用它熟悉的方式来生成代码了。
该 Pass 不可调参
该 Pass 依赖 DominatorTreeAnalysis
。核心函数是 runImpl
。步骤如下:
- 扫描并收集所有 indirectbr
- 遍历函数中的每个基本块,找出以 indirectbr 结尾的指令。
- 如果某条 indirectbr 没有任何目标,就把它替换成一个 “不可达”(unreachable)指令并删除;否则把它和它所有可能的目标块记下来。
- 确定需要编号的目标基本块
- 对所有被上述 indirectbr 指令引用(即其 successor)的基本块做一次筛选:只有那些地址被取用(blockaddress)的块才会被考虑。
- 给每个这样的基本块分配一个连续的正整数编号(从 1 开始)。
- 把块地址常量替换成整数
- 把原来表示块地址的常量(blockaddress)用“把编号转成指针”(inttoptr)的形式代替。
- 这样后面的跳转逻辑就可以统一看作是基于一个整数值,而非直接的指针比较。
- 处理没有任何地址取用的情况
- 如果在上一阶段发现根本没有一个块的地址被取用(编号列表为空),说明所有的 indirectbr 都无法分辨目标,统一把它们删掉并用 unreachable 取代,就结束了。
- 生成 switch 架构
- 只有一个 indirectbr:
- 直接在它所在的基本块内,把那条 indirectbr 换成一个 switch,以预先计算好的整数作为控制值,把各编号映射到对应的块。
- 多个 indirectbr:
- 新建一个专门的 “switch 基本块”。
- 在每个原先的 indirectbr 块里,用普通的分支跳到这个新块,并把各自转换好的整数值通过 PHI 汇聚到一起。
- 在新块里以 PHI 得到的整数值为开关,生成一个统一的 switch,把不同编号跳转到对应的目标块。
- 只有一个 indirectbr:
- (可选)同步更新支配树
- 如果上层启用了支配树更新器,会在删除旧的分支边和插入新分支边时,累积一系列增删操作,最后一次性把支配树结构更新完,保证后续的分析一致性。
评论