php8 use-def链

代码

php 复制代码
 $a = 10;
  if ($cond) {
      $a = 20;
  }
  echo $a;

数据结构 (zend_ssa.h)

整个 SSA 系统的核心是三个结构体:

复制代码
zend_ssa (顶层容器)
  ├── cfg: zend_cfg              ← 控制流图(基本块、前驱/后继、支配树)
  ├── blocks[]: zend_ssa_block   ← 每个基本块的 phi 链表头
  ├── ops[]: zend_ssa_op         ← 每条指令的 SSA 信息(use/def 编号 + 链表)
  ├── vars[]: zend_ssa_var       ← 每个 SSA 变量的 def-use 信息
  └── vars_count                 ← SSA 变量总数

zend_ssa_op (每条指令在 SSA 中的视图):

c 复制代码
typedef struct _zend_ssa_op {
      int op1_use;       // op1 读的是哪个 ssa_var (≥0) 或 -1
      int op2_use;       // op2 读的是哪个 ssa_var (≥0) 或 -1
      int result_use;    // result 读的是哪个 ssa_var (≥0,少见) 或 -1
      int op1_def;       // op1 定义的是哪个 ssa_var (≥0) 或 -1
      int op2_def;       // op2 定义的是哪个 ssa_var (≥0) 或 -1
      int result_def;    // result 定义的是哪个 ssa_var (≥0) 或 -1
      int op1_use_chain; // 该 use 在使用链表中的下一个 use 指令编号
      int op2_use_chain;
      int res_use_chain;
  } zend_ssa_op;

zend_ssa_var (每个 SSA 变量的元信息):

c 复制代码
typedef struct _zend_ssa_var {
      int var;                 // 原始变量编号(CV#0, CV#1, TMP#last_var+0...)
      int scc;                 // 强连通分量
      int definition;          // 定义该 ssa_var 的指令编号(入口)
      int use_chain;           // 使用该 ssa_var 的第一个 use 指令编号(链表头)
      zend_ssa_phi *definition_phi;  // 如果是 phi 定义的,指向 phi
      zend_ssa_phi *phi_use_chain;   // 该变量在 phi 中的使用列表
      unsigned int alias : 2;  // 是否可能被间接修改(符号表别名等)
  } zend_ssa_var;

zend_ssa_phi (phi 函数):

c 复制代码
typedef struct _zend_ssa_phi {
      zend_ssa_phi *next;    // 同一 BB 中的下一个 phi
      int pi;                // ≥0 表示 e-SSA Pi 节点(来自哪个前驱 BB)
      int var;               // 原始 CV/VAR/TMP 变量号
      int ssa_var;           // 该 phi 定义的 SSA 变量编号
      int block;             // 所在基本块
      zend_ssa_phi **use_chains;  // 每个 source 的使用链表
      int *sources;           // phi 的源 SSA 变量号数组(每个前驱一个)
  } zend_ssa_phi;

三阶段构建流程 (zend_build_ssa, zend_ssa.c:996)

阶段 0:控制流图 & 支配者树

在 SSA 构建之前,zend_build_cfg() 已经完成了:

复制代码
CFG:
    BB0 [start=0, len=2]   (ASSIGN $a,10; JMPZ $cond,BB2)
    │  successors: BB1, BB2
    ├─▶ BB1 [start=2, len=2]   (ASSIGN $a,20; JMP BB2)
    │    successors: BB2
    └─▶ BB2 [start=4, len=2]   (ECHO $a; RETURN)
         successors: (exit)
         
 支配者树 (dominator tree):
    BB0 为根(函数入口)
    BB1: idom = BB0  (BB0 支配 BB1)
    BB2: idom = BB0  (BB0 支配 BB2)
    BB2 的前驱: BB0, BB1 (这是合并点!)

  预排序索引:
    BB0: level=0, children→BB1→BB2
    BB1: level=1
    BB2: level=1

阶段 1: DFG 构建 zend_build_dfg() + def 传播 + Phi 放置 (zend_build_ssa, line 1017-1116)

Step 1a: DFG 收集 def/use 集合 (zend_build_dfg, zend_dfg.c:252)

遍历每个 BB 的每条指令,逐条调用 _zend_dfg_add_use_def_op() (zend_dfg.c:22):

复制代码
BB0:
    OP0  ASSIGN $a, 10
         → op1_type=IS_CV($a): zend_bitset_incl(use, $a)  ← $a 的旧值被"使用"(覆盖前)
         → 然后 goto add_op1_def (line 213): zend_bitset_incl(def, $a)
         → result_def 无
         BB0: use={$a}  def={$a}

    OP1  JMPZ $cond, BB2
         → op1_type=IS_CV($cond): zend_bitset_incl(use, $cond) ← 读 $cond
         → 无 def
         BB0: use={$a, $cond}  def={$a}

  BB1:
    OP2  ASSIGN $a, 20
         → 同上: use={$a}, def={$a}
         BB1: use={$a}  def={$a}

    OP3  JMP BB2
         → 无 use, 无 def

  BB2:
    OP4  ECHO $a
         → op1_type=IS_CV($a): zend_bitset_incl(use, $a)
         BB2: use={$a}  def={}

    OP5  RETURN 1
         → IS_CONST, 无 use

Step 1b: 计算 liveness (in/out) (zend_build_dfg, line 288-310)

经典的数据流不动点迭代:in = use ∪(out - def), out = ∪in(successors):

复制代码
一回合:
    BB2: out={}  in=use={$a}
    BB1: out=in(BB2)={$a}  in=use={$a} ∪({$a} - def={$a}) = {$a}
    BB0: out=in(BB1)∪in(BB2)={$a} in=use={$a,$cond} ∪({$a} - {$a}) = {$a, $cond}

Step 1c: 传播 def + 确定 Phi 位置 (line 1044-1116)

这是核心。算法逐条传播 def 集合,对于每个 CFG 合并点(前驱数 > 1 的 BB),沿支配者边往上走,找到需要 phi 的变量。

复制代码
初始化: def_j = 每个 BB 的局部 def 集合
           phi_j = {} (空)

  迭代:
    BB2 (predecessors_count=2 > 1, 是合并点):
      对每个前驱 k:
        k=BB0: i=BB0, i != idom(BB2)=BB0? 否 → 停止 (BB0 直接支配 BB2)
        k=BB1: i=BB1, i != idom(BB2)=BB0? 是
                → phi_j |= phi_j ∩ def(BB1) ∩ in(BB2)
                → phi(BB2) = {} ∩ {$a} ∩ {$a} = {}
                → 然后 i = idom(BB1) = BB0 = idom(BB2) → 停止

      首次迭代 phi_2 = {},全都满足 → 不变更新 def_2 的 phi 部分
      但是: def(BB0)={$a}, def(BB1)={$a}, in(BB2)={$a}
      实际算法在 line 1062 用 union_with_intersection:
      phi_2 |= phi_2 ∩ def(i) ∩ in(BB2)

      等一下,这个逻辑是: phi_j = U_{predecessor k} (phi_j ∩ def(i) ∩ in(j))
      对于所有在 idom 之上的 i。当 i != idom(j) 时继续上行。

      首轮: phi_2 = {} → 交集为空 → phi_2 = {}
      但 zend_bitset_subset(phi_2, def_2, set_size)?
        def(BB2) = {}, phi_2 = {} → 是子集 → changed=0

      ... 实际上首轮不变,但问题在于 BB0 的 def={$a} 没到达 BB2 覆盖 def(BB2)...

      实际上这个算法有问题需要第二轮。让我简化解释...

实际上 PHP 的算法更精妙。在标准教科书法中,phi 放置

dominance frontier 上。

PHP 采用一种变体:

c 复制代码
for each block j with >1 predecessors:
   for each predecessor k:
     i = k
     while i != idom(j):
       phi_j |= def_i ∩ in_j    // 在非直接支配者的路径上,任何"被定义且活跃"的变量需要 phi
       i = idom(i)

实际结果(简化后的 PHP 算法):

复制代码
对于 BB2 (predecessors=BB0, BB1, idom=BB0):
    前驱 BB0: i=BB0 == idom → 不进入 while
    前驱 BB1: i=BB1 != idom → 进入 while
      phi(BB2) |= def(BB1) ∩ in(BB2)
              |= {$a} ∩ {$a}
              |= {$a}
      i = idom(BB1) = BB0 == idom(BB2) → 退出

  结果: phi(BB2) = {$a}
        def(BB2) = {} → phi ⊄ def → def(BB2) |= phi → def(BB2) = {$a}
        changed = 1 → 再次迭代

  第二次迭代:
    phi(BB2) = {$a}, def(BB0) = {$a}, def(BB1) = {$a}
    phi |= phi ∩ def(BB1) ∩ in(BB2) = {$a} ∩ {$a} ∩ {$a} = {$a}
    不变 → def(BB2)已经包含 {$a} → 没有新phi → 收敛

在合并点创建 phi 指令 (line 1083-1116)

复制代码
在 BB2 中为 $a 创建 phi:
    phi->pi = -1  (普通 phi, 不是 e-SSA Pi)
    phi->var = 0  (CV#0, 即 $a)
    phi->ssa_var = -1  (renaming 阶段分配)
    phi->sources[0] = -1  (来自 BB0 前驱, 待填充)
    phi->sources[1] = -1  (来自 BB1 前驱, 待填充)
    phi->block = 2

阶段 2: 变量重命名 (zend_ssa_rename, line 914)

这是经典 Cytron 算法的 renaming pass。按照支配树的前序遍历进行。

关键数据结构:

c 复制代码
var[]  // var[原始var_num] = 当前活跃的 SSA 变量号
         // 初始值: var[0]=0 ($a), var[1]=1 ($cond) 等
         // 初始 for CV: var[i] = i (line 1128)
         // 对于 TMP_VAR: var[i] = -1 (line 1125)

递归遍历支配树 (使用 worklist 模拟递归,line 914-988):

复制代码
进入 BB0 (n=0):
    // 1. 处理 BB0 的 phi (BB0 没有 phi)

    // 2. 遍历 BB0 的指令 (zend_ssa_rename_in_block, line 820)
    OP0  ASSIGN $a, 10
         → op1_type=IS_CV → op1_use = var[0] = 0 (ssa_var[0])  ← 读 $a 的旧值
         → opcode==ZEND_ASSIGN → goto add_op1_def:
              op1_def = ssa_vars_count = 2  ← 创建新的 ssa_var
              var[0] = 2  ← 更新 $a 的活跃版本
              ssa_vars_count++ → 3
         → ssa_ops[0]: {op1_use:0, op1_def:2}

    OP1  JMPZ $cond, BB2
         → op1_type=IS_CV → op1_use = var[1] = 1 (ssa_var[1])
         → result_type=IS_TMP_VAR → result_def = ssa_vars_count = 3
              var[last_var+0] = 3  ← TMP 变量也需要 rename
              ssa_vars_count++ → 4
         → ssa_ops[1]: {op1_use:1, result_def:3}

    // 3. 填充后继 BB 的 phi sources
    BB0 的后继: BB1, BB2
    对于 BB2 的 phi $a (ssa_var 尚为 -1):
      phi->sources[0] = var[0] = 2  ← 填充 BB0→BB2 边上的值

    // 4. 递归处理子节点
    先 BB1 (level=1), 再 BB2 (level=1)

  进入 BB1 (n=1):
    // 1. 无 phi

    // 2. 遍历指令
    OP2  ASSIGN $a, 20
         → op1_use = var[0] = 2  ← 当前 $a 活跃版本仍是 ssa_var[2](来自 BB0)
         → 等等! 这是 ASSIGN, no_val 标注:
              zend_ssa_is_no_val_use(line 221) 返回 true
              → op1_use 存在但被标记为 no_val (值不重要)
         → add_op1_def: op1_def = ssa_vars_count = 4  ← 新版本
              var[0] = 4  ← $a 的活跃版本更新为 4
         → ssa_ops[2]: {op1_use:2, op1_def:4}

    OP3  JMP BB2
         → 无 use, 无 def

    // 3. 填充后继 BB2 的 phi sources
    对于 BB2 的 phi $a:
      phi->sources[1] = var[0] = 4  ← 填充 BB1→BB2 边上的值

    // 4. BB1 没有子节点 (leaf) → 直接回溯
    var[0] 恢复为 2 (退出 BB1 时回滚 var 数组)

  进入 BB2 (n=2, BB0 的另一个子节点):
    // 1. 处理 BB2 的 phi (zend_ssa_rename_in_block, line 829)
    phi for $a:
      phi->ssa_var = ssa_vars_count = 5  ← 为 phi 分配 ssa_var 编号
      var[0] = 5  ← $a 的活跃版本现在变成 phi 的结果

    OP4  ECHO $a
         → op1_type=IS_CV → op1_use = var[0] = 5  ← 使用 phi 结果!
         → ssa_ops[4]: {op1_use:5}

    OP5  RETURN 1
         → IS_CONST → 无 SSA 参与

    // 3. BB2 无后继 → 不再填充 phi sources
    // 4. 无子节点 → 回溯

构建 use-def 链表 (zend_ssa_compute_use_def_chains, line 1144)

这是最关键的一步。从后往前扫描所有指令,为每个被使用的 ssa_var 建立从 def 到所有 use 的单链表。

重命名后的 ssa_ops (opN_use / opN_def):

复制代码
  OP0  ASSIGN:  op1_use=0,  op1_def=2      ssa_ops[0]
  OP1  JMPZ:    op1_use=1,  result_def=3    ssa_ops[1]
  OP2  ASSIGN:  op1_use=2,  op1_def=4       ssa_ops[2]
  OP3  JMP:     (无 SSA)                     ssa_ops[3]
  OP4  ECHO:    op1_use=5                    ssa_ops[4]
  OP5  RETURN:  (无 SSA)                     ssa_ops[5]

BB2 phi: ssa_var=5, sources=2, 4 (来自 BB0→2, BB1→4)

逆向扫描建立链表 (line 1167-1193):

c 复制代码
 // ssa_vars 每位初始化为:
  //   var = -1
  //   scc = -1
  //   definition = -1
  //   use_chain = -1   ← 链表头,-1 表示空

  for (i = op_array->last - 1; i >= 0; i--) {  // 从最后的指令往前
      zend_ssa_op *op = ssa->ops + i;

      // 链入 use 链表 (头插法!)
      if (op->op1_use >= 0) {
          op->op1_use_chain = ssa_vars[op->op1_use].use_chain;  // 保存旧链头
          ssa_vars[op->op1_use].use_chain = i;                   // 新链头 = 当前指令
      }
      // 对 op2_use, result_use 同理

      // 设置 definition 指针
      if (op->op1_def >= 0) {
          ssa_vars[op->op1_def].definition = i;   // 指向定义指令
      }
      // 对 op2_def, result_def 同理
  }

逆向扫描的执行过程:

复制代码
i=5  OP5 RETURN: 无 use, 无 def → 无事

  i=4  OP4 ECHO: op1_use=5
       → ssa_ops[4].op1_use_chain = ssa_vars[5].use_chain = -1
       → ssa_vars[5].use_chain = 4          ← ssa_var[5] 被 OP4 使用

       ssa_vars[5]: use_chain = 4

  i=3  OP3 JMP: 无

  i=2  OP2 ASSIGN: op1_use=2, op1_def=4
       → op1_use_chain = ssa_vars[2].use_chain = -1
       → ssa_vars[2].use_chain = 2          ← ssa_var[2] 被 OP2(op1) 使用
       → ssa_vars[4].definition = 2          ← ssa_var[4] 由 OP2 定义

       ssa_vars[2]: use_chain = 2
       ssa_vars[4]: definition = 2

  i=1  OP1 JMPZ: op1_use=1, result_def=3
       → op1_use_chain = ssa_vars[1].use_chain = -1
       → ssa_vars[1].use_chain = 1          ← ssa_var[1] 被 OP1(op1) 使用
       → ssa_vars[3].definition = 1          ← ssa_var[3] 由 OP1 定义

       ssa_vars[1]: use_chain = 1
       ssa_vars[3]: definition = 1

  i=0  OP0 ASSIGN: op1_use=0, op1_def=2
       → op1_use_chain = ssa_vars[0].use_chain = -1
       → ssa_vars[0].use_chain = 0          ← ssa_var[0] 被 OP0(op1) 使用
       → ssa_vars[2].definition = 0          ← ssa_var[2] 由 OP0 定义

       ssa_vars[0]: use_chain = 0
       ssa_vars[2]: definition = 0
       ssa_vars[2]: use_chain = 2  (之前已有)

最后处理 phi 的 use 链 (line 1196-1210):

c 复制代码
for (i = 0; i < ssa->cfg.blocks_count; i++) {
      zend_ssa_phi *phi = ssa->blocks[i].phis;
      while (phi) {
          phi->block = i;
          ssa_vars[phi->ssa_var].var = phi->var;
          ssa_vars[phi->ssa_var].definition_phi = phi;  // phi 是定义
          // ... 填充 phi 到 source 的使用链表
          phi = phi->next;
      }
  }

  // BB2 的 phi: sources=[2, 4], ssa_var=5
  // ssa_vars[5].definition_phi = phi
  // ssa_vars[5].definition = -1   (不是指令定义的,是 phi 定义的)
  // ssa_vars[2] 的 phi_use_chain 中包含此 phi (ssa_var[2] 是这个 phi 的一个 source)
  // ssa_vars[4] 的 phi_use_chain 中包含此 phi

最终完整的 use-def 链

复制代码
ssa_var[0]: $a 的初始版本 (入口值)
        definition = -1 (函数入口,无定义指令)
        use_chain = 0 ──▶ OP0 (ASSIGN, op1_use, no_val)

    ssa_var[1]: $cond 的初始版本
        definition = -1
        use_chain = 1 ──▶ OP1 (JMPZ, op1_use)

    ssa_var[2]: $a 的第 2 个版本 (=10)
        definition = 0  ← 由 OP0 (ASSIGN $a, 10) 定义
        use_chain = 2 ──▶ OP2 (ASSIGN, op1_use, no_val)
        phi_use_chain ──▶ phi in BB2, source[0]

    ssa_var[3]: JMPZ 结果 (条件跳转的布尔值)
        definition = 1
        use_chain = -1 (未使用)

    ssa_var[4]: $a 的第 3 个版本 (=20)
        definition = 2 ← 由 OP2 (ASSIGN $a, 20) 定义
        use_chain = -1 (通过 phi 使用,不在指令链表中)
        phi_use_chain ──▶ phi in BB2, source[1]

    ssa_var[5]: phi($a, BB0→2, BB1→4) 的结果
        definition_phi = phi in BB2
        definition = -1
        use_chain = 4 ──▶ OP4 (ECHO, op1_use)

图示化:


遍历 use-def 链的 API

从 def 遍历所有 use:

c 复制代码
#define FOREACH_USE(var, use) do { \
      int _var_num = (var) - ssa->vars, next; \
      for (use = (var)->use_chain; use >= 0; use = next) { \
          next = zend_ssa_next_use(ssa->ops, _var_num, use);

  // zend_ssa.h:193-203 --- 辅助函数
  static zend_always_inline int zend_ssa_next_use(
      const zend_ssa_op *ssa_op, int var, int use)
  {
      ssa_op += use;   // 指针移到 use 所在的 ssa_op
      if (ssa_op->op1_use == var) {
          return ssa_op->op1_use_chain;  // 返回链表中下一个 use
      } else if (ssa_op->op2_use == var) {
          return ssa_op->op2_use_chain;
      } else {
          return ssa_op->res_use_chain;  // result_use 情况
      }
  }

从 phi source 遍历 phi 使用:

c 复制代码
// zend_ssa.h:277-284
  #define FOREACH_PHI_USE(var, phi) do { \
      int _var_num = (var) - ssa->vars; \
      zend_ssa_phi *next_phi; \
      for (phi = (var)->phi_use_chain; phi; phi = next_phi) { \
          next_phi = zend_ssa_next_use_phi(ssa, _var_num, phi);

  // zend_ssa.h:205-218
  static zend_always_inline zend_ssa_phi* zend_ssa_next_use_phi(
      const zend_ssa *ssa, int var, const zend_ssa_phi *p)
  {
      if (p->pi >= 0) {
          return p->use_chains[0];   // Pi 节点只有一个 source
      } else {
          // 遍历 sources 找到匹配的,返回对应的 use_chain
          for (int j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
              if (p->sources[j] == var) {
                  return p->use_chains[j];
              }
          }
      }
  }

优化阶段如何使用 use-def 链

DCE 死代码消除 (dce.c)

DCE 沿 use 链反向 传播活性。从 RETURN/ECHO 等"必然活跃"的指令出发,将它们的操作数标记为活跃,再标记操作数的定义指令为活跃,如此递归。

c 复制代码
// dce.c:294 --- 操作数入队
  static void add_operands_to_worklists(...) {
      if (ssa_op->op1_use >= 0) {
          if (!zend_bitset_in(instr_worklist, ssa_var[op1_use].definition)) {
              zend_bitset_incl(instr_worklist, ssa_var[op1_use].definition);
          }
      }
      // op2_use 同理
  }

  // dce.c:326 --- 检查变量是否"死"
  static bool is_var_dead(zend_ssa *ssa, int var_num, ...) {
      // 检查 use_chain 中是否所有 use 都是死指令
      // 检查 phi_use_chain 中是否所有 phi use 都是死
  }

具体追踪:

复制代码
ECHO $a 必然 active → ssa_var[5] 活跃 → 沿着 use 链:

  1. use_chain=4: OP4 活跃 (ECHO 本身)

  2. ssa_var[5] 的 definition_phi 指向 BB2 的 phi
     → 遍历 phi 的所有 sources [2, 4]:
       → ssa_var[2] 活跃, ssa_var[4] 活跃

  3. ssa_var[2] 活跃 → ssa_var[2].definition = 0 → OP0 活跃
     → ssa_var[2] 的 phi_use_chain (作为 phi source) → 已检查

  4. ssa_var[4] 活跃 → ssa_var[4].definition = 2 → OP2 活跃

  5. OP0 活跃 → 但 op1_use = ssa_var[0] 是 no_val (值不重要)
     → ssa_var[0] 不标记活跃 (初始版本未被"真正"使用)

结果: OP0, OP2, OP4 都活着 → 没有死代码

SCCP 常量传播 (sccp.c)

SCCP 用 use-def 链的反向:从 def 找 use。

当 SCCP 发现一个变量是常量,通过 replace_constant_operands() 遍历 use 链,把所有 use 替换为常量。

c 复制代码
// sccp.c:2377 --- replace_constant_operands
  static int replace_constant_operands(sccp_ctx *ctx) {
      for (int i = op_array->last_var; i < ssa->vars_count; i++) {
          if (!IS_TOP(&ctx->values[i]) && !IS_BOT(&ctx->values[i])) {
              // 是个常量!遍历所有 use 替换
              FOREACH_USE(&ssa->vars[i], use) {
                  try_replace_op1(ctx, opline+use, ssa_op+use, i, &ctx->values[i]);
                  try_replace_op2(ctx, opline+use, ssa_op+use, i, &ctx->values[i]);
              } FOREACH_USE_END();

              // 也遍历 phi_use_chain
              FOREACH_PHI_USE(&ssa->vars[i], phi) {
                  // 在 phi 中也替换源
              } FOREACH_PHI_USE_END();
          }
      }
  }

类型推断 (zend_inference.c)

类型推断沿 SSA 指令顺序处理,但遇到循环时需要迭代到不动点。

对于每个 ssa_op,op1 和 op2 的 use 给出 ssa_var_infouse.type,然后根据操作码语义计算 result 类型。

c 复制代码
// zend_inference.c 中对 ADD 的处理
  case ZEND_ADD:
      t1 = ssa->var_info[ssa_op->op1_use].type;  // 查 use 的类型
      t2 = ssa->var_info[ssa_op->op2_use].type;
      tmp = binary_op_result_type(ssa, ZEND_ADD, t1, t2, ...);
      UPDATE_SSA_TYPE(tmp, ssa_op->result_def);   // 更新 def 的类型

总结:双向链表 vs 单向链

PHP 的 SSA use-def 是两个方向的索引:

方向 存储位置 用途
def → use (正向) ssa_var.use_chain + opN_use_chain DCE(反向活性传播)、SCCP(常量替换到 use 点)
use → def (反向) ssa_var.definition / ssa_var.definition_phi 任何时候需要知道"谁定义了这个值"(每个优化都会用到)

链表为什么用头插法(逆向扫描)?

c 复制代码
// zend_ssa_compute_use_def_chains, line 1167
  for (i = op_array->last - 1; i >= 0; i--) {
      // 头插法: 更晚的 use 在链表头部
      op->op1_use_chain = ssa_vars[op->op1_use].use_chain;  // 旧链头变新节点next
      ssa_vars[op->op1_use].use_chain = i;                   // 新链头 = 当前
  }

逆向+头插的结果是 use 链按指令从后往前排列,这对 DCE 有利(DCE 是反向传播,靠后的 use 先处理符合直觉),而且不需要二次遍历(一次扫描同时建立 definition 和 use 链)。

为什么 op1_use 和 op1_def 可以同时非 -1?

a = a + 1 编译为 ASSIGN_OP a, 1(复合赋值),这条指令既读 a 的旧值,又写 $a 的新值。

所以:

  • op1_use = ssa_var旧版本 ← 读旧值做运算
  • op1_def = ssa_var新版本 ← 写回新值

这就是 SSA 的核心约定:每条指令最多定义一个新的 SSA 变量号,但可以读多个旧的 SSA 变量号。

相关推荐
evans在进步1 小时前
LeetCode 189 轮转数组:三次反转原地解决,图解 Java 实现
java·算法·leetcode
歌_顿2 小时前
Drawing_To_Model
算法
龍德明宇2 小时前
AI不会疼-龍德明宇
人工智能·算法·大语言模型llm·负主体性·ai存在论
302wanger2 小时前
程序员写 SOP:把脑子里的流程固化下来
算法
科学实验家3 小时前
完全背包
算法
Smoothcloud润云3 小时前
GPU租赁数据安全怎么做?
人工智能·算法·ai·aigc·gpu算力·gpu
呜喵王阿尔萨斯3 小时前
extern “C“ in C++
c语言·c++·算法
乐观勇敢坚强的老彭3 小时前
C++信奥静态数组和动态数组
数据结构·c++·算法
月光船幽幽4 小时前
呼吸孔设计重塑系统稳定性
python·科技·算法