静态单赋值(二)—gcc中的SSA化算法

版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/119336399

更多内容可关注微信公众号  

一、基本概念[1]

    许多数据流分析需要寻找表达式中每个定值变量的使用点,或者每个使用变量的定值点. 定值-使用链表(def-use chain)是一种能够高效获取这些信息的数据结构: 对于流图中的每条语句,编译器能够保存两个由指针组成的列表:

    * 其中一个列表中的指针指向在该语句中定值的变量的所有使用点

    * 另一个列表中的指针指向该语句中所使用变量的所有定值点

    通过这个方法编译器能够快速地从使用跳到定值, 从定值跳到使用.

    对定值-使用链思想的一种改进是静态单赋值形式(static single-assignment form),或SSA形式.

    SSA形式是这样一种中间表示: 在程序中文中,每个变量只有一个定值,这个(静态的)定值可能位于一个可(动态)执行多次的循环中,因此我们把它称为静态单赋值形式,而不是简单地称为单赋值形式(在单赋值形式中,变量根本不会被重新定值)

    在直线式代码中变量的重新定值很容易被发现,但当两条控制流边汇合到一起时,则需要一个额外的虚构的φ函数来合并来自各个边的变量定值, 如图:

   此时需要在汇合后的基本块(如4)的开头插入 a3 = φ(a1,a2); 指令来虚构出一个定值合并的操作; 

   φ函数需要知道执行到当前基本块(bb)时控制流是从哪一边来的,而其最简单的实现方式就是最后(phi函数展开时)在当前基本块的所有前驱基本块中都插入一个对变量a3的赋值即可; 

   φ函数作为隐式运行时变量定值显式编译时变量定值的中间转换, 既解决了编译时的静态单赋值转化问题, 又满足了运行时代码逻辑的正确性.

1. 插入φ函数的标准(方法)

    一个程序转化为SSA形式的基本步骤首先是为变量插入φ函数,然后用下标重命名变量的所有定值和使用, 插入φ函数最简单的方法是在每一个汇合点(即前驱个数>1的结点),为每个变量都插入一个φ函数, 此时SSA化的过程十分简单,因为对于一个bb来说,其中的任何一个变量在bb的入口都会被再次定值,故此时按照任意顺序bb,并对每个bb中用到的变量进行重命名即可(重命名后需要修改其所有后继中所有phi函数中此bb对应的一个参数).

   但这种做的时间&空间效率并不好, 既浪费且又必要,故通常的做法是通过1)路径汇合标准/2)迭代的必经结点边界标准算法来确定φ函数的插入,并通过支配树的DFS遍历来对函数进行SSA化. 由[1]C19.1.2可知,对于一个变量在这两种算法下插入的φ函数是完全一致的, 而gcc中则是通过迭代的必经结点边界算法来实现的.

2. 迭代的必经结点边界标准:

    * 必经结点边界标准: 只要结点x包含某个变量a的一个定值, 则x的必经结点边界中的人和街店z都需要有一个a的φ函数

    * 迭代的必经结点边界标准: 由于φ函数本身也是一种定值, 我们必须迭代地应用必经结点边界标准,直到再没有结点需要φ函数位置

    对于某个变量而言,一个结点最多为其插入一个phi函数, 在迭代过程中无需重复插入,因为phi函数的参数来自此结点的所有前驱结点, 一个phi函数就包含了此变量来自所有前驱的所有可能的定值.

3. SSA形式的必经结点性质:

  静态单赋值形式的一个基本性质就是, 定值(结点)是使用(结点)的必经结点:

  1) 如果x是基本块n中一个φ函数的第i个参数,那么x的定值(结点)是n的第i个前驱的必经结点;

  2) 如果x在基本块n的一个非φ函数中被使用,则x的定值(结点)是n的必经结点

4. gcc中SSA化的算法描述:

  SSA化的流程可以简化为:

  1) 对所有变量插入必要的phi函数

  2) 依次重写各个变量的定值/使用(转化为SSA格式)

  而具体到gcc中则大体分为4个步骤:

  1) 计算当前函数的支配结点边界

      按照迭代的必经结点边界算法, 要想确定phi函数插入的位置,必须要先根据函数的CFG确定函数的支配结点边界(支配结点边界的定义和算法见[2]), 在gcc中则是

      1) 先通过calculate_dominance_info计算当前函数的支配结点信息

      2) 再通过compute_dominance_frontiers计算当前函数的支配结点边界信息

  2) 遍历并记录函数每条指令中出现的变量的defs/uses信息

      此步主要是收集函数每条指令中出现的变量的defs/uses信息,这些信息保存在标量的bitmap中, 故可以按照任意的顺序遍历指令,而在gcc中则是采用基于支配树的深度优先遍历(DFS)顺序遍历各个基本块(bb)中的一条条指令.

  3) 为每个变量计算phi函数的插入点

      1)中已经计算出了每个bb的支配结点边界信息, 2)中计算出了每个变量的defs/uses信息, 按照迭代的必经结点边界算法, 此变量每一个def出现的结点的必经结点边界结点均需要插入一个phi函数, 插入后的phi函数作为此变量的一个新定值(def)点参与迭代计算.

      针对当前函数中的每个变量均需要通过此算法计算出其需要插入phi函数的结点信息,并在对应结点为每个变量分别插入phi函数

  4) 按照支配树的DFS遍历, 重写每个bb的每条指令,将整个函数SSA化

      这里和步骤2)不同, SSA化的过程必须按照支配树的深度优先算法遍历结点,

      1) 对于每个结点顺序需遍历所有语句,对于每条语句:

        1) 若发现一个操作数中涉及变量使用(use)时,需要将此操作数改为对此变量在DFS上最新定义(SSA_NAME)的使用

        2) 若发现一个操作数中涉及变量定值(def)时,需要为此变量新创建一个SSA_NAME作为此变量的最新定义, 并将此此操作数改为新创建的SSA_NAME

      2) 语句处理完毕后续为此结点的每个后继结点的所有phi函数确定一个参数(phi函数的第x个参数来自其第x个前驱结点):

        phi函数对应原始变量在4.4)-1)-2)中的最新定值(def)则为phi函数的第x个参数,代表运行时来若控制流来自第x个前驱,则当前参数x的定值应来自哪个SSA_NAME


二、gcc中ssa化的整体流程

    在gcc中 pass_build_ssa 负责对一个函数进行首次ssa转化,而 update_ssa则负责对转化后的ssa的更新, pass_build_ssa大体流程如下:

/*
  pass_build_ssa负责对函数cfun的SSA化,整个SSA化可以分为4步:
  1.计算当前函数的支配结点边界信息                                                //compute_dominance_frontiers (dfs);
  2.以支配树的DFS遍历为顺序, 标记每条指令中的def/use信息到各个全局变量              //mark_def_dom_walker (CDI_DOMINATORS).walk (fun->cfg->x_entry_block_ptr);
  3.根据1中的支配节点边界信息和2中统计的每个变量的def/use信息, 确认每个
    变量需要在哪些bb中插入phi函数,并为每个变量插入对应个数的phi函数                 //insert_phi_nodes (dfs);
  4.以支配树的DFS遍历为顺序,遍历所有bb并重写每条指令中的所有use/def操作数(SSA化)    //rewrite_blocks
    1) 在遍历到每个bb时执行:
       1) 将bb中所有phi函数的返回值,写入到其对应变量的当前定义中(currdef)
       2) 遍历bb中所有指令序列,并重写其中的变量:
          1) 当发现一个变量的use时,用其currdef这个SSA_NAME替换指令中原有的use操作数
          2) 当发现一个变量的def时,为其新创建一个SSA_NAME替换指令中原有的def操作数,并将此SSA_NAME记录到此变量的currdef中,以及入栈
       3) 重写此bb所有后续bb中每个phi函数的第i个参数,i为当前bb在其后续bb的前驱bbs中的编号,phi函数的第i个参数被修改为其对应变量var在当前bb中最后的def(currdef)
    2) 在每个bb的所有sub bb都遍历结束后:
       1) 将4-1-2-2)中栈中保存的所有def都恢复到其对应变量的currdef中, 以保证当前bb分析完毕其父bb中的变量定义不变,继续rename其兄弟bb时才能保证正确
*/
unsigned int pass_build_ssa::execute (function *fun)
{
  bitmap_head *dfs;
  basic_block bb;
  init_ssa_operands (fun);        /* 此函数主要为当前函数分配了virtual use/def操作的虚拟操作数vop */
  init_ssa_renamer ();              /* 标记当前函数尚未处于SSA格式, 初始化记录此函数中每个变量def/use的 var_infos 数组 */

  /* 根据此函数基本块的数目分配一个一维的bitmap并初始化, 后续use/def分析中会在此bitmap中标记出现了use/def的bb(大部分都会出现) */
  interesting_blocks = sbitmap_alloc (last_basic_block_for_fn (fun));
  bitmap_clear (interesting_blocks);

  /* 根据此函数基本块数目,分配并初始化一个二维的bitmap矩阵(dfs[x][x]),后续此bitmap用来记录此函数中每个bb的支配结点边界信息 */
  dfs = XNEWVEC (bitmap_head, last_basic_block_for_fn (fun));
  FOR_EACH_BB_FN (bb, fun)
    bitmap_initialize (&dfs[bb->index], &bitmap_default_obstack);

  /* step1:根据此函数(cfun)的CFG寄存器支配树和每个bb的支配结点边界[2], 支配节点边界是判断是否需要插入phi函数的关键,最终dfs中:
        * dfs[x]记录bb x的支配节点边界
        * dfs[x][y] = true; 代表y是x节点的一个支配节点边界节点
  */
  calculate_dominance_info (CDI_DOMINATORS);      /* 这里先根据CFG计算此函数的支配树,支配树以一个个支配结点结构体保存在各个 bb->doms[0]中 */
  compute_dominance_frontiers (dfs);              /* 根据支配树信息(bbs->doms), 计算所有bb的支配结点边界信息并记录到二维bitmap dfs中 */

  /* step2:按照DFS算法遍历此函数的支配树中每个结点(bbs->doms),对于每个结点都要遍历其整个指令序列(bb->gimple_df->seqs)中的
    每一条语句(stmt),并将每条语句的def/use信息更新到 stmt->vuse/vdef/use_ops的同时,也更新到全局的var_infos/interest_blocks
    (针对整个函数),以及m_kills(针对当前bb)中 */
  mark_def_dom_walker (CDI_DOMINATORS).walk (fun->cfg->x_entry_block_ptr);

  /* step3: 根据支配结点边界信息和每个变量的def/use信息,为每个变量在必要的bb中插入此变量的phi函数, 一个bb中所有插入的phi函数均记录在其bb->gimple_df->phi_nodes链表中,
    按照算法, 某个变量var若在某个bb中出现了def,则此bb的支配结点边界中所有的bbs都要插入var的phi函数
    需要注意的是:此时插入的phi函数只有返回值结点(为var新生成的SSA_NAME),其参数列表为空,只有在其前驱结点重写后phi函数的参数才能得以填充 */
  insert_phi_nodes (dfs);

  /* step4: 按照DFS算法再次遍历函数的支配树,并重写每个bb中的每条指令stmt(也就是SSA化), stmt中的每个use要改为对齐currdef(SSA_NAME)的use, 
    每个def都要新建一个新的SSA_NAME; 对于一个bb的SSA化又分为三步:
     1. 将此bb中所有phi函数的返回值设置为其对应变量的currdef
     2. 遍历此bb中所有stmt, 将其中所有use的操作数改为其currdef; 若碰到def则为其原始变量var生成一个新的SSA_NAME,将def的操作数和currdef替换为新的SSA_NAME
     3. 遍历此bb在函数CFG中所有的后继bb, 填充后继bb中所有phi指令的一个参数(第x个参数,x为此bb在其后继bb的所有前驱结点中的编号)
  */
  rewrite_blocks (ENTRY_BLOCK_PTR_FOR_FN (fun), REWRITE_ALL);

  ......
  return 0;
}

三、step2: 函数指令序列的def/use分析

   step2中要分析当前函数中所有变量的def/use信息,这是通过函数mark_def_dom_walker::walk()实现的:

class mark_def_dom_walker : public dom_walker        /* 没有单独实现 walk函数,继承父类walk函数 */
{
public:
  mark_def_dom_walker (cdi_direction direction);
  ~mark_def_dom_walker ();
  virtual edge before_dom_children (basic_block);
private:
  bitmap m_kills;                /* 此bitmap可以看做是一个以变量UID为下标的一维数组, m_kills[x] = 1;则代表UID为x的变量在当前分析的bb中出现了kill def */
};

class dom_walker
{
  ......
  void walk (basic_block);
  ......
}

/*
   在此函数执行前,bb所在的函数必须已经计算过了支配树(calculate_dominance_info),此时计算结果已经保存到各个bb->doms中了,
  而此函数则通过深度优先遍历算法(DFS)遍历从bb开始的整个支配树的所有结点,且:
   * 在刚遍历到某个结点时,为当前结点调用before_dom_children(basic_block);回调
     - 若此回调返回STOP,则其所有子节点不必再遍历
     - 若此回调返回非STOP,则按照DFS继续遍历其子结点
   * 在某个结点(及其子节点)遍历完毕后, 为当前结点调用after_dom_children(basic_block);回调
*/
void dom_walker::walk (basic_block bb)
{
  int sp = 0;
  basic_block dest;
  basic_block *worklist = XNEWVEC (basic_block, n_basic_blocks_for_fn (cfun) * 2);    /* 为后续递归遍历分配临时的结点栈 */
  ......

  while (true)
    {
      /* 只遍历有前驱的bb或函数出入口bb */
      if (EDGE_COUNT (bb->preds) > 0 || bb == ENTRY_BLOCK_PTR_FOR_FN (cfun) || bb == EXIT_BLOCK_PTR_FOR_FN (cfun))
      {
        edge taken_edge = NULL;
        taken_edge = before_dom_children (bb);    /* 在遍历到一个结点时,先调用 before_dom_children 回调 */
        worklist[sp++] = bb;                      /* 将当前节点放到worklist中,作为栈帧标记(NULL为分隔符),后续会先递归处理其子节点 */
        worklist[sp++] = NULL;
        if (taken_edge != STOP)                    /* 若回调返回的非 STOP(-1) */
        {
          int saved_sp = sp;
          /* 遍历结点bb在支配树中的所有子节点,支配树信息在walk之前应该已经计算出(calculate_dominance_info),并记录在各个bb->doms中 */
          for (dest = first_dom_son (m_dom_direction, bb); dest; dest = next_dom_son (m_dom_direction, dest))
            worklist[sp++] = dest;                /* 在栈中记录当前bb的所有要遍历的支配树中的子结点 */
          if (sp - saved_sp > 1 && m_dom_direction == CDI_DOMINATORS && m_bb_to_rpo)        /* 若对支配树的 DFS遍历有顺序要求,则将bb的子节点排序以确保后续的遍历顺序 */
            sort_bbs_postorder (&worklist[saved_sp], sp - saved_sp);
        }
      }
           
      while (sp > 0 && !worklist[sp - 1])         /* 若当前bb没有子结点,或所有子节点都处理完毕,则为当前结点调用处理完毕后的回调, while循环负责处理多级结点返回 */    
      {
        --sp;                          /* 忽略前面标记的NULL分隔符 */
        bb = worklist[--sp];           /* 处理上一层的下一个bb */
               ......
        after_dom_children (bb);       /* 当前结点及其子节点遍历完毕的回调函数 */
      }
    
      if (sp)
        bb = worklist[--sp];           /* 递归遍历worklist中下一个结点 */
      else
        break;                         /* 整个支配树结点的DFS遍历完毕, 退出循环 */
    }
  
  free (worklist);
}

  由上可知, mark_def_dom_walker (CDI_DOMINATORS).walk (fun->cfg->x_entry_block_ptr); 实际上是通过其父类的walk函数从当前函数的入口bb开始根据DFS算法遍历了此函数的支配树,并对每个bb调用了回调函数mark_def_dom_walker::before_dom_children,此函数定义如下:

edge mark_def_dom_walker::before_dom_children (basic_block bb)
{
  gimple_stmt_iterator gsi;
  bitmap_clear (m_kills);            /* 重新清空记录当前bb中kill defs的数组 */
  
  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))            /* 遍历当前bb中所有gimple语句 */
    /* 分析每条语句中的def/use信息,结果更新到stmt->vuse/vdef/use_ops的同时也
       将统计信息更新到全局变量var_infos,interesting_blocks,以及参数m_kills中 */
    mark_def_sites (bb, gsi_stmt (gsi), m_kills);
  return NULL;
}

static void mark_def_sites (basic_block bb, gimple *stmt, bitmap kills)
{
  tree def;
  use_operand_p use_p;
  ssa_op_iter iter;

  update_stmt (stmt);    /* 分析stmt中的 virutal use/def, real use信息,并将其更新到当前语句 stmt->vuse/vdef/use_ops中 */
  ......
  set_register_defs (stmt, false);    /* 默认标记当前stmt中不存在操作数的def/use操作 */
  set_rewrite_uses (stmt, false);
  ......

  FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_ALL_USES)    /* 遍历此stmt中的所有uses的操作数 */
  {
      tree sym = USE_FROM_PTR (use_p);                  /* 获取当前处理的操作数中的变量树节点信息 */
      ......
      /*
         在当前bb的每条语句分析过程中(见下面的循环)如果发现了某变量的def,那么此def对于当前bb来说就是个kill def,
        而如果分析到一个变量的use前还没有在当前bb中发现其kill def,则说明此变量的值来自其前驱bb, 那么此时就要
        标记此变量在当前bb中出现了传播使用(set_livein_block)
      */
      if (!bitmap_bit_p (kills, DECL_UID (sym)))        /* 若此变量在当前bb中未出现kill def */
        set_livein_block (sym, bb);                     /* 在变量的全局var_info->def_blocks->livein_blocks中标记,变量sym在基本块bb中为传播使用 */
      set_rewrite_uses (stmt, true);                    /* 标记当前语句stmt中有需要被重写的use操作数 */
  }

  FOR_EACH_SSA_TREE_OPERAND (def, stmt, iter, SSA_OP_ALL_DEFS)    /* 动态分析当前stmt的所有defs, 并遍历所有被def的变量 */
  {
      ......
      set_def_block (def, bb, false);                  /* 在当前变量的 var_info->def_blocks->def_blocks 中标记在基本块bb中出现了此变量的def */
      bitmap_set_bit (kills, DECL_UID (def));          /* 在per bb的kills数组中标记当前bb中出现了变量def的 kill def */
      set_register_defs (stmt, true);                  /* 在当前语句stmt中标记其中出现了某操作数的def操作 */
  }

  /* 当当前分析的bb中任何一条语句(stmt)中出现了操作数的use/def操作,当前bb就要被标记为interesting. 除了特别简单的bb,基本上大部分bb都会标记到interesting_blocks中 */
  if (rewrite_uses_p (stmt) || register_defs_p (stmt))
    bitmap_set_bit (interesting_blocks, bb->index);
}

四、step3: 为每个变量插入phi函数

  分析完def/use信息后,则需要为当前函数的每个变量在需要的位置(bb)处插入phi函数,此过程是在函数 insert_phi_nodes中实现的:

/* 此函数负责为当前函数中所有变量在需要插入phi函数的bb中插入phi函数, phi函数全部记录在每个bb->gimple_df->phi_nodes中
  需要注意的是此时phi函数的参数尚未填充,只有返回值填充了(其对应变量派生的SSA_NAME)
*/
static void insert_phi_nodes (bitmap_head *dfs)
{
  var_info *info;
  ......
  /*
    var_infos[]中记录了当前函数所有变量(local_decls)在整个函数中的def/use信息, 而在def/use分析中简单的排除了一些不需要插入phi函数的变量(need_phi_state = NEED_PHI_STATE_NO的变量,
   这样的变量在整个函数中只存在一个def,且其所有use点所在的bb都被def所在的bb支配),而vars数组中最终只记录了那些可能需要插入phi函数的变量做二次分析和处理
    */
  auto_vec<var_info *> vars (var_infos->elements ());
  FOR_EACH_HASH_TABLE_ELEMENT (*var_infos, info, var_info_p, hi)
    if (info->info.need_phi_state != NEED_PHI_STATE_NO)
      vars.quick_push (info);
  vars.qsort (insert_phi_nodes_compare_var_infos);        /* 根据变量的UID排序 */

  FOR_EACH_VEC_ELT (vars, i, info)                         /* 遍历当前函数中每个可能要插入phi结点的变量(info为迭代器) */
    {
      /*
        * info->info.def_blocks.def_blocks 记录info->var这个变量在各个bb中的def信息(bitmap)
        * dtf记录整个函数所有bb的支配结点边界信息
        此函数根据二者计算出需要为当前变量var插入phi函数的所有bb的信息,返回的idf同样是一个bitmap,若idf[i]-1,则代表在编号为i的bb中需要为变量info->var插入phi函数
      */
      bitmap idf = compute_idf (info->info.def_blocks.def_blocks, dfs);
      /* 根据idf这个bitmap, 向其对应的bb中为变量info->var创建一个gphi指令,并将此指令添加到 bb->gimple_df->phi_nodes中, 
        ifd是标准算法算出的phi函数插入点,而此函数在创建前还会再次裁剪 idf bitmap(对于变量kill def且不存在livein use的bb,无需插入phi函数) */
      insert_phi_nodes_for (info->var, idf, false);
      BITMAP_FREE (idf);
    }
}

/*
   此函数负责根据变量var的def信息(def_blocks)和整个函数所有bb的支配结点边界信息,(dfs) 计算出需要为此函数插入phi函数的bb信息(bitmap phi_insertion_points)并返回此bitmap
  phi函数的算法是:
  1) 当前变量x所有的def点(bb)的支配结点边界中所有结点都需要插入phi函数
  2) 插入的phi函数相当于对变量x的隐式def,故插入了phi函数的结点(bb)的支配结点边界中所有结点也都要插入phi函数
  3) 针对一个变量x, 任何一个bb中只需要为其插入一个phi函数即可,无需重复插入,因为phi函数参数来自于所有前驱,一个phi函数就代表了所有可能的隐式def;
*/
bitmap compute_idf (bitmap def_blocks, bitmap_head *dfs)
{
  bitmap_iterator bi;
  unsigned bb_index, i;
  bitmap phi_insertion_points;

  auto_vec<int> work_stack (2 * n_basic_blocks_for_fn (cfun));

  /* 此bitmap为函数的返回值, 其是一个一维数组,下标为bb在当前函数的索引号, phi_insertion_points[i] = 1;则代表当前bb中需要为变量x插入phi函数 */
  phi_insertion_points = BITMAP_ALLOC (NULL);

  /* 按照算法, phi函数需要插入到变量 x所有def点所在bb的支配结点边界结点中, 这里首先将所有出现变量x def点的bb加入到队列中 */
  EXECUTE_IF_SET_IN_BITMAP (def_blocks, 0, bb_index, bi)
    work_stack.quick_push (bb_index);

  while (work_stack.length () > 0)
    {
      bb_index = work_stack.pop ();        /* 获取下一个存在变量x def的bb的编号 */
      /*
        &dfs[bb_index] 记录索引号为bb_index的bb的支配结点边界信息(下标为bb索引号, dfs[bb_index][i] = 1; 则代表编号为i的bb为编号为 bb_index的bb的支配结点) 
        此宏展开后会依次比较 dfs[bb_index] 和phi_insertion_points 中的每一个bit,若 dfs[bb_index][i] = 1; 而phi_insertion_points[i] = 0; 则代表发现了
       新的需要插入phi函数的bb(编号为i),此时会将i push到work_stack中,下一次循环后会递归遍历此bb;
           */
      EXECUTE_IF_AND_COMPL_IN_BITMAP (&dfs[bb_index], phi_insertion_points, 0, i, bi)
      {
        work_stack.quick_push (i);    /* 结点i因插入了φ函数,其支配结点边界结点也要加入到 phi_insertion_points中 */
        bitmap_set_bit (phi_insertion_points, i);    /* 标记结点i中需要插入φ函数 */
      }
    }

  return phi_insertion_points;
}

/*
   此函数为变量var在需要插入phi函数的bb中插入gphi指令(包括创建,派生var的一个SSA_NAME作为gphi返回值,并将gphi插入到bb->gimple_df->phi_nodes指令序列中)
  在插入前会尝试删除一些不需要插入phi函数的点(如果变量var在某个bb中不存在liven_use,且存在kill_def,则此bb无需插入phi函数)
*/
static void insert_phi_nodes_for (tree var, bitmap phi_insertion_points, bool update_p)
{
  unsigned bb_index;
  edge e;
  gphi *phi;
  basic_block bb;
  bitmap_iterator bi;
  def_blocks *def_map = find_def_blocks_for (var);    /* 此结构体中存着变量var的多个bitmap,包括 kill def/livein use */
  ......

  /* def_blocks和 livein_blocks都是在 各个bb的mark_def_sites中分析出的:
     * def_blocks[i] = 1;代表变量var在编号为i的bb中出现了kill def;
     * livein_blocks[i] = 1; 代表变量var在编号为i的bb中出现了传播使用(也就是第一次访问是use,而不是def)
     而在一个bb中,如果一个变量没有出现livein,且存在kill def,那么这个bb中是不需要插入phi函数的,简单说就是:在一个bb的顺序指令执行过程中,
    一个变量先被赋值然后才被使用,那么在SSA化时其前驱bb对此变量的def就不必考虑了(因为没有人用), 但如果变量在此bb中没有def则必须插入phi函数,
    否则会影响到此bb后续bb中var的定值.
     这里就是从phi_insertion_points中去除了livein_use,且有kill def的bb,这些bb无需插入phi函数
    */
  prune_unused_phi_nodes (phi_insertion_points, def_map->def_blocks, def_map->livein_blocks);

  /* phi_insertion_points 中剩余的结点均需要为变量var插入phi函数,这里遍历其中每个bb */
  EXECUTE_IF_SET_IN_BITMAP (phi_insertion_points, 0, bb_index, bi)
  {
    bb = BASIC_BLOCK_FOR_FN (cfun, bb_index);    /* 根据bb_index获取bb的basic_block结构体 */

    if (update_p)
      mark_block_for_update (bb);

    if (TREE_CODE (var) == SSA_NAME) {    /* SSA_NAME 是在 update_ssa中用到的,先pass */
      ......
    } else {         /* 正常变量是走这里 */
      /*
        创建一个gphi指令,为变量var生成一个新的SSA_NAME设置为此gphi的返回值结点(所以phi函数的返回值SSA_NAME的version编号都较小),
       gphi指令的所有参数都暂不填充(因为所有的指令尚未SSA化,无法填充), 并将新生成的gphi指令链接到当前bb->gimple_df->phi_nodes指令序列的末尾
      */
      phi = create_phi_node (var, bb);
      ......
    }
    set_register_defs (phi, true);        /* 标记当前gphi语句中存在def操作(因为其返回值结点被def了) */
    mark_phi_for_rewrite (bb, phi);
  }
}

五、step4: 指令重写(SSA化)

  phi函数插入完毕后,需要对整个函数所有bb中的所有指令序列中所有的def/use变量进行重写,此过程是在函数rewrite_blocks 中完成的:

static void rewrite_blocks (basic_block entry, enum rewrite_mode what)
{
  ......
  if (what == REWRITE_ALL)
      /*
         按照DFS遍历当前函数的支配树,并在:
         * 遍历到某个bb时执行回调 rewrite_dom_walker::before_dom_children
         * 遍历完成某个bb(及其所有sub bb)后执行回调 rewrite_dom_walker::after_dom_children
      */
      rewrite_dom_walker (CDI_DOMINATORS).walk (entry);        /* pass_build_ssa时走这里 */
  else if (what == REWRITE_UPDATE)
      rewrite_update_dom_walker (CDI_DOMINATORS).walk (entry);
  else
    gcc_unreachable ();
  ......
}

struct def_blocks
{
  bitmap def_blocks;       /* 记录当前变量在哪些bb中出现了kill def */
  bitmap phi_blocks;
  bitmap livein_blocks;    /* 记录当前变量在哪些bb中出现了livein use */
};

struct common_info
{
  ENUM_BITFIELD (need_phi_state) need_phi_state : 2;    /* 记录def/use分析中简单的判断结果,即当前变量是否不需要插入phi函数 */
  tree current_def;                                     /* 记录此变量在rewrite_stmt中当前的定义(通常是其一个SSA_NAME) */
  struct def_blocks def_blocks;                         /* 记录此变量在所有bb中的kill def/livein use 信息 */
};

struct var_info
{
  tree var;             /* 此var_info中记录了哪个变量的信息 */
  common_info info;     /* 记录此变量的def/use,currdef等相关信息 */ 
};

/*
   此函数负责在DFS遍历到支配树中每个结点bb时,将其中的所有stmt SSA化, SSA化的流程分为三步:
   1. 将此bb中所有phi函数的返回值设置为其对应变量的currdef
   2. 遍历此bb中所有stmt, 将其中所有use的操作数改为其currdef; 若碰到def则为其原始变量var生成一个新的SSA_NAME,将def的操作数和currdef替换为新的SSA_NAME
   3. 遍历此bb在函数CFG中所有的后继bb, 填充后继bb中所有phi指令的一个参数(第x个参数,x为此bb在其后继bb的所有前驱结点中的编号)
*/
edge rewrite_dom_walker::before_dom_children (basic_block bb)
{
  ......
  block_defs_stack.safe_push (NULL_TREE);        /* 在stack中做一个标记,代表当前开始处理一个新的bb */
  /*
     对于一个变量x来说,其phi函数的作用就是在SSA化的过程中为phi函数所在bb整合此变量在其各个前驱bb中的定义, 
    故在SSA化的重写阶段,若一个变量在某个bb中有phi函数,那么phi函数的返回值(x的一个SSA_NAME,见insert_phi_nodes)会作为分析此bb时x的最新定义.
     这里是将当前bb的所有phi函数的返回值,设置为其对应变量的最新定义(变量的var_info.currdef)
  */
  for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
    {
      /*
         获取gphi指令的返回值结点,其必为一个SSA_NAME结点,见phi函数的创建过程
        pass_build_ssa => insert_phi_nodes => insert_phi_nodes_for => create_phi_node
      */
      tree result = gimple_phi_result (gsi_stmt (gsi));        /* 获取此phi函数返回值结点 */

      /* 将返回值结点设置为其原始变量的currdef, 如:
         若当result 为SSA_NAME x_1, 则 SSA_NAME_VAR (result)为变量x,这里设置 x的var_info.curredef = (tree)x_1; 
      */
      register_new_def (result, SSA_NAME_VAR (result));        
    }

  if (bitmap_bit_p (interesting_blocks, bb->index))    /* 若当前bb在前面def/use分析中发现其中至少存在一个def/use */
    /* 遍历当前bb中的每一条语句,并修改每条语句中需要被重写的操作数,注意这里遍历的是bb->gimple_df->seqs, 而不是phi_nodes */
    for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
      rewrite_stmt (&gsi);    /* 此函数负责对一条指令(stmt)进行重写 */

  /* 当前bb重写完毕(SSA化完毕),则所有的变量都已经变为其SSA形式了,此时需要为其所有后继结点填充所有phi函数的第i个参数, i为此bb在其后继的前驱结点中的编号 */
  rewrite_add_phi_arguments (bb);
  return NULL;
}

/*
   此函数负责重写si->ptr这条语句(stmt)中的所有def/use,将此stmt SSA化, 其处理顺序是先处理use后处理def,这也符合正常一条指令执行的逻辑,stmt中:
  * 发现所有对原始变量var的use的操作数指针,都会被改为(指向)对其currdef这个SSA_NAME结点
  * 发现所有对原始变量var的def,都会先为变量var新生成一个SSA_NAME,并将def操作数的指针改为(指向)新SSA_NAME结点,并将新的SSA_NAME更新到变量var的currdef
*/
static void rewrite_stmt (gimple_stmt_iterator *si)
{
  gimple *stmt = gsi_stmt (*si);

  /* 若当前语句中没有需要被重写的use/def,则直接返回 */
  if (!rewrite_uses_p (stmt) && !register_defs_p (stmt))     return;

  if (rewrite_uses_p (stmt))        /* 1.若当前语句中存在需要被重写的use operands,则先重写uses所有的uses操作数 */
  {
    ......
    /* 遍历当前stmt的real use链表(stmt->use_ops),其中记录了当前stmt SSA化时需要被修改的USE操作数, 并将其修改为对应变量的当前SSA_NAME定义
       如 y = x; 若此指令解析前x的当前定义为x_1,则这里将指令修改为 y = x_1; */
    FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_ALL_USES)
    {
        /* use_p->use 指向此stmt的一个操作数的内存,这里先获取其原始操作数var(即 var = *use_p->use) */
        tree var = USE_FROM_PTR (use_p);    
        ......
        /* 修改此操作数为变量var的当前定义(currdef), 即 *use_p->use = var_1; (若currdef为var_1) */
        SET_USE (use_p, get_reaching_def (var));
    }
  }

  if (register_defs_p (stmt))        /* 2. 若当前语句中存在需要被重写的def operands(见mark_def_sites),则重写所有的defs操作数 */
    FOR_EACH_SSA_DEF_OPERAND (def_p, stmt, iter, SSA_OP_ALL_DEFS)
    {
      tree var = DEF_FROM_PTR (def_p);    /* 从stmt中的def操作数中读取当前被def的变量的树节点 */
      tree name;
      ......
      if (TREE_CODE (var) == SSA_NAME) continue;    /* 忽略SSA_NAME */
      gcc_checking_assert (DECL_P (var));
      ......
      name = make_ssa_name (var, stmt);             /* 为变量var新的def点重新分配一个var的SSA_NAME */
      SET_DEF (def_p, name);                        /* 将stmt某操作数中的变量var修改为其SSA_NAME,将定义转化为静态单赋值形式 */
      /* 在支配树的DFS遍历中, 变量var的当前定义此时已经变为了新生成的SSA_NAME name, 将其更新到变量var的currdef中,后续再出现的use则使用此新的def,
         需要注意的是这里,这里会在全局block_defs_stack中保存所有旧的def,在rewrite_dom_walker::after_dom_children会恢复已有修改, 因为
         在支配树的DFS遍历中若有 A=>B, A=>C; 那么分析B时产生的def只作用于B以及其所有sub bb, 而当回到A重新遍历C时要重新使用A中的def继续分析,
         所以这里会有一个入栈操作.after_dom_children中完成出栈操作 */
      register_new_def (DEF_FROM_PTR (def_p), var); 
      ......
    }
}

/* 在一个bb及其所有sub bb处理完成后,要恢复此bb中的所有currdef,恢复后才可以继续遍历此bb的兄弟bb,此函数则完成currdef的恢复操作 */
void rewrite_dom_walker::after_dom_children (basic_block bb ATTRIBUTE_UNUSED)
{
  while (block_defs_stack.length () > 0)    /* 遍历def栈中的所有内容, 其插入点是上面的register_new_def函数 */
  {
      tree tmp = block_defs_stack.pop ();   /* 获取stack中一个要恢复的SSA_NAME */
      tree saved_def, var;
      if (tmp == NULL_TREE) break;          /* 遍历当NULL_TREE代表当前bb之前的所有入栈处理完毕,NULL_TREE在栈中作为分隔符分隔各个bb的栈信息 */

      if (TREE_CODE (tmp) == SSA_NAME) {    /* 大多数情况下之前push进来的结点为某个变量的SSA_NAME结点 */
        saved_def = tmp;                    /* 当前需要恢复的SSA_NAME */
        var = SSA_NAME_VAR (saved_def);     /* 获取SSA_NAME的原始变量 */
        ......
      } else {                              /* 非SSA_NAME则说明push到原始变量了,说明在当前bb的父节点之前没有变量var的def出现 */
       saved_def = NULL;                    /* 没有def出现时标记saved_def为空 */
       var = tmp;                           /* tmo即为原始变量 */
      }
     /* var为原始变量,先找到其var_info结构体,然后恢复其之前定义为 saved_def */
     get_common_info (var)->current_def = saved_def;
  }
}

/* 
  此函数负责遍历当前bb在函数CFG中的所有后继bb, 遍历每个后继bb中的每一条phi指令,其目的是为了填充所有bb中所有phi函数的一个参数,
  后继bb中所有phi函数的参数都来自其前驱bb,在其前驱bb SSA化后要立即为其所有后续bb添加SSA化后的参数
*/
static void rewrite_add_phi_arguments (basic_block bb)
{
  edge e;
  edge_iterator ei;
  FOR_EACH_EDGE (e, ei, bb->succs)    /* 遍历当前bb的所有直接后继bb,这里遍历的是边 */
  {
    gphi *phi;
    gphi_iterator gsi;
    for (gsi = gsi_start_phis (e->dest); !gsi_end_p (gsi); gsi_next (&gsi))    /* 遍历每个后继bb中的所有phi语句 */
    {
      phi = gsi.phi ();
      res = gimple_phi_result (phi);        /* 获取phi函数的返回值节点, 也就是此gphi语句中最终def的那个 SSA_NAME */
      /*
        SSA_NAME_VAR (res))获取ssa name 对应的var节点, 而get_reaching_def 则获取var变量当前最新的def是哪个变量,
        如res可能是var_1 = φ(,...) 中的var_1(SSA_NAME),而返回的currdef是var的最新定义(可能是var_3,也是个SSA_NAME)           
      */
      currdef = get_reaching_def (SSA_NAME_VAR (res));
      ......
      /* 将currdef记录到 phi函数的第x个参数中(phi.arg[x].def),并增加currdef这个SSA_NAME的use链表(将phi.arg[x].imm_use
        链接到currdef.imm_use使用链中),这里的x是 e这条边在e->dest结点中的前驱边编号(当前bb在其某后继结点中的前驱结点编号)
      */
      add_phi_arg (phi, currdef, e, loc);
    }
  }
}

参考资料:

[1]《现代编译原理—C语言描述》

[2] 静态单赋值(一)—gcc中的支配树 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值