CFG(控制流图)
Control Flow Graph(控制流图) 是编译器和 JIT 对程序控制流进行分析与优化的核心数据结构。
基本概念
CFG 是一个有向图:
- 节点(Node)= 基本块(Basic Block):顺序执行、无分支的指令序列,只有一个入口和一个出口
- 边(Edge):表示可能的控制转移(条件跳转、无条件跳转、异常路径等)
┌──────────┐
│ Entry │
└────┬─────┘
│
┌────▼─────┐
│ Block A │ ← 基本块:连续无跳转指令
│ i = 0 │
│ i < 10? │
└──┬────┬──┘
│yes │no
┌────▼──┐ └──►┌──────────┐
│Block B│ │ Exit │
│ i++ │ └──────────┘
└────┬──┘
│
└──────────► Block A (回边 Back Edge)
回边(Back Edge)标志着循环的存在,是 JIT 循环优化的关键识别点。
在 JVM/JIT 中的作用
JIT 编译器(C1 / C2)在将字节码编译为机器码之前,会先构建 CFG,再在其上执行多轮优化:
字节码
│
▼ 构建 CFG(基本块划分)
CFG
│
├─ 支配树分析(Dominator Tree)
├─ 活跃变量分析(Liveness Analysis)
├─ 循环识别(Loop Detection)
│
▼ 优化 Pass
├─ 死代码消除(Dead Code Elimination)
├─ 公共子表达式消除(CSE)
├─ 循环不变量外提(Loop Invariant Code Motion)
├─ 内联展开(Inlining)
└─ 逃逸分析(Escape Analysis)
│
▼ 寄存器分配 → 机器码
基本块划分规则
编译器按以下规则切割指令序列为基本块:
- 函数/方法入口是新块的起点
- 跳转指令(
goto、if、tableswitch等)的目标是新块起点 - 跳转指令之后的下一条指令是新块起点
- 异常处理器(
catch块)的起点是新块起点
支配关系(Dominance)
若从 Entry 到达节点 B 的每条路径都经过节点 A,则称 A 支配 B(A dom B)。
支配树用于:
- SSA 构造(静态单赋值形式,JIT 内部表示)
- 循环检测(回边 = 从 B 到其支配者 A 的边)
- 代码外提(将循环不变量提到循环支配节点之前)
与 javac 的关系
javac 编译器自身也在语义分析阶段构建 CFG,用于:
| 用途 | 说明 |
|---|---|
| 明确赋值分析 | 确认变量在使用前已被赋值(Definite Assignment) |
| 可达性分析 | 检测不可达代码(unreachable code) |
| 异常检查 | 验证 checked exception 是否被捕获或声明 |
调试与可视化
打印 C2 IR(Ideal Graph):
# 需要 debug 版 JVM 或 fastdebug 构建
java -XX:+PrintIdealGraph -XX:PrintIdealGraphLevel=1 MainClassGraal 编译器可视化(推荐,生产 JDK 可用):
# 使用 GraalVM 的 Ideal Graph Visualizer
java -Dgraal.Dump=:1 -Dgraal.PrintGraph=Network MainClass