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。步骤如下:

  1. 扫描并收集所有 indirectbr
    • 遍历函数中的每个基本块,找出以 indirectbr 结尾的指令。
    • 如果某条 indirectbr 没有任何目标,就把它替换成一个 “不可达”(unreachable)指令并删除;否则把它和它所有可能的目标块记下来。
  2. 确定需要编号的目标基本块
    • 对所有被上述 indirectbr 指令引用(即其 successor)的基本块做一次筛选:只有那些地址被取用(blockaddress)的块才会被考虑。
    • 给每个这样的基本块分配一个连续的正整数编号(从 1 开始)。
  3. 把块地址常量替换成整数
    • 把原来表示块地址的常量(blockaddress)用“把编号转成指针”(inttoptr)的形式代替。
    • 这样后面的跳转逻辑就可以统一看作是基于一个整数值,而非直接的指针比较。
  4. 处理没有任何地址取用的情况
    • 如果在上一阶段发现根本没有一个块的地址被取用(编号列表为空),说明所有的 indirectbr 都无法分辨目标,统一把它们删掉并用 unreachable 取代,就结束了。
  5. 生成 switch 架构
    • 只有一个 indirectbr
      • 直接在它所在的基本块内,把那条 indirectbr 换成一个 switch,以预先计算好的整数作为控制值,把各编号映射到对应的块。
    • 多个 indirectbr
      • 新建一个专门的 “switch 基本块”。
      • 在每个原先的 indirectbr 块里,用普通的分支跳到这个新块,并把各自转换好的整数值通过 PHI 汇聚到一起。
      • 在新块里以 PHI 得到的整数值为开关,生成一个统一的 switch,把不同编号跳转到对应的目标块。
  6. (可选)同步更新支配树
    • 如果上层启用了支配树更新器,会在删除旧的分支边和插入新分支边时,累积一系列增删操作,最后一次性把支配树结构更新完,保证后续的分析一致性。