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

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

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

一、需要了解的基本概念

在gcc优化的过程中,不论是循环的优化还是数据流分析,支配树在其中都起到至关重要的作用:

  * 对于循环来说,支配树对找出代码中的循环非常有用

  * 对数据流分析来说,支配树对计算SSA_NAME中的phi函数插入点十分重要

支配树中的主要概念包括:

  1) 必经结点(Dominator,又称支配结点): 如果从(如函数的)起始节点S0到结点n的所有有效路径都经过结点d, 那么结点d则成为结点n的必经结点,同时每一个结点都是自己的必经结点

      简单的说,必经结点的意思就是,从S0开始,如果控制流走到了n,那么在此之前其一定走过了结点d, 此时记为dom(n) = d;

  2) 严格必经结点: 若d是n的必经结点,且d!=n,则d是n的严格必经结点;

  3) 直接必经结点(Immediate Dominator,又称直接支配结点):

      首先对于毕竟节点有定理如下: 如果d,e都是n的必经结点,则d一定是e的必经结点或e一定是d的必经结点;

      而直接必经结点的定义为: 若i是n的严格必经结点,且i不是n的其他必经结点的必经结点,则i是n的直接必经结点;

      简单说就是 i是距离n最近的n的(非!=n的)必经结点, 即为 idom(n) = i;

  4) 必经结点树(Dominator Tree,又称支配树):

      一棵树中包含图中的所有节点, 但只为每个做一条从其直接支配节点到自身的边(也就是对于所有节点n,只有 idom(n) => n的边), 这样构成的一颗树即为此图的必经结点树;

      对于流图来说, 由于其中的每一个结点都至少有一个必经结点(即起始结点), 且每个结点都只能有一个直接必经结点,故一个流图一定能画出其对应的必经结点树.且若流图中每个节点均可达,则对应的必经结点树中的每个结点也必可达. 以[1] C18.1.2为例,其中流图和其对应的支配树的关系如图:

     5) 必经结点边界(Dominance Frontier, 又称支配结点边界): 结点x的必经结点边界是所有符合下面条件的结点w的集合: x是w前驱的必经结点,但不是w的严格必经结点;

        简单说就是: 首先支配结点边界是针对某个结点来说, 其是多个节点的集合; 支配结点是当前结点和当前结点前驱的其他后继结点可能的控制流汇集处(也是在转SSA过程中需要插入phi函数的位置), 以[1] C19.1.2为例:

        其中结点5的必经结点为{4,5,12,13}, 这四个结点都是结点5和其他结点控制流的汇集处;


二、gcc中支配树相关结构体

    支配树是针对流图的,而在gcc中则是针对函数的控制流图(CFG)的, 故一个函数的支配树信息最终是保存在函数内的(实际上是各个bb->dom中), 在gcc中和支配树相关的结构体有三个:

    1) enum dom_state: 此枚举型中记录当前函数中支配树的状态

enum dom_state
{
  DOM_NONE,              /* 代表当前函数的支配树还没有计算 *
  DOM_NO_FAST_QUERY,     /* 代表当前函数的支配树已经计算好了(记录在各个bb->dom中), 但尚未建立快速查询 */
  DOM_OK                 /* 代表当前函数支配树的快速查询也建立好了(更新好了bb->dom.dfs_num_in/out字段), 只有支配树和快速查询均建立好了才会有DOM_OK状态 */
};

    2) class dom_info: 此类可以保存一个支配树的完整信息,但在gcc中通常都是临时的保存支配树算法的结果(最终结果保存在bb->dom中)

class dom_info
{
  ......
  TBB *m_dfs_parent;             /* Lengauer-Tarjan 算法中需要借助DFS来实现高效的支配树计算,而此结构体实际上是一个数组,其下标为函数CFG在DFS下每个结点的父节点编号 */
  TBB *m_key;
  TBB *m_path_min;
  TBB *m_bucket;
  TBB *m_next_bucket;
  TBB *m_dom;                    /* 这里同样是一个数组, 其中m_dom[x] 记录DFS中编号为x的结点的直接必经结点编号 */
  TBB *m_set_chain;
  unsigned int *m_set_size;
  TBB *m_set_child;
  TBB *m_dfs_order;              /* 记录基本块在DFS遍历中的编号,在DFS中编号从1开始 */
  TBB *m_dfs_last;               /* 指向 m_dfs_order中最后一个bb */
  basic_block *m_dfs_to_bb;      /* 记录DFS中每个节点对应的bb的 basic_block 结构体指针 */
  unsigned int m_nodes;
  bitmap m_fake_exit_edge;
  unsigned m_n_basic_blocks;     /* 记录当前支配树中的结点个数,实际上来自函数中基本块的个数 */
  bool m_reverse;                /* 代表当前记录的是否为后序支配信息, gcc中可以计算支配树和后序支配树两种, 此值来自参数 CDI_DOMINATORS/CDI_POST_DOMINATORS */
  basic_block m_start_block;     /* 记录当前函数的入口bb */
  basic_block m_end_block;       /* 记录当前函数的出口bb */
};

    3) struct et_node: 此结构体实际上是代表元素树(Element Tree)中一个结点信息的,而在gcc中,每个bb均通过此元素来保存其支配树结点

struct basic_block_def {
  ......
  struct et_node * dom[2];    /* 在基本块(bb)中, dom[0/1]分别记录此bb在其支配树和后序支配树(若有)中的结点信息, 整个函数的支配树信息实际上就是记录在由入口bb开始的各个bb的dom中 */
  ......
}

struct et_node
{
  void *data;                     /* 在支配树中,data指向当前支配结点对应的基本块bb的指针 */
  int dfs_num_in, dfs_num_out;    /* 记录此结点在其对应的树结点的DFS遍历过程中,进/出此结点时遍历到的结点编号. 对于支配树来说,这两个字段负责支配树的快速查询 */
  struct et_node *father;         /* 记录当前节点的父节点,在支配树中当前节点的父节点为其直接支配节点(idom)*/
  struct et_node *son;            /* 当前节点的第一个子节点指针,在支配树中则是最后插入的子节点 */
  struct et_node *left;           /* 当前结点的左右兄弟结点 */
  struct et_node *right;          /* The brothers of the node.  */
  struct et_occ *rightmost_occ;   /* The rightmost occurrence.  */
  struct et_occ *parent_occ;      /* The occurrence of the parent node.  */
};

三、gcc中支配树的计算

    在gcc中是通过calculate_dominance_info来计算当前函数的支配树的, 在调用此函数之前要求当前全局的cfun记录当前要计算的函数, 计算结果会以一个个支配结点的形式记录在当前函数的各个基本块的bb->dom中, 而并不是以一个支配树结构体(如dom_info)保存的,函数定义如下:

/*
   此函数为当前函数cfun计算[后序]支配树信息(dir决定是否为后序),计算结果以支配结点的方式保存到cfun的各个bb->dom中,并将当前函数的支配树计算状态更新到 cfun->cfg->x_dom_computed (即dom_computed)中;
   若此函数已计算了支配树,则此函数需重新计算并验证之前计算的支配树的正确性,如果二者不匹配直接报错,匹配则返回不做任何修改.
*/
void calculate_dominance_info (cdi_direction dir)
{
  unsigned int dir_index = dom_convert_dir_to_idx (dir);      /* 先确定本次是要计算支配树(dir=0)还是后序支配树(dir=1),后序均以后序支配树举例 */

  if (dom_computed[dir_index] == DOM_OK)      /* 若当前函数已有支配树,则对当前函数重新计算支配树信息,并和原有的比较看是否正确 */
    {
      checking_verify_dominators (dir);       /* 重新计算的支配树(dom_info)和函数中已有的支配树(各bb->dom)信息匹配,则直接返回 */
      return;
    }

  if (!dom_info_available_p (dir))            /* 若当前函数的支配树是完全不可用的(DOM_NONE),则在此循环中重新计算支配树 */
    {
      gcc_assert (!n_bbs_in_dom_tree[dir_index]);          /* 支配树未计算时,cfg中支配节点数量应给为0 */

      basic_block b;
      FOR_ALL_BB_FN (b, cfun)                 /* 在开始计算前先为当前函数cfun的每个bb都分配一个元素树结点,以存储其在支配树中的支配结点的信息 */
        b->dom[dir_index] = et_new_tree (b);
      n_bbs_in_dom_tree[dir_index] = n_basic_blocks_for_fn (cfun);        /* 在cfg中记录当前支配树中结点的个数,支配树中结点个数正常和函数中的结点个数是相同的 */

      dom_info di (cfun, dir);                /* 创建并初始化一个临时的dom_info类,用来先保存整个支配树的计算结果,参数是要计算的函数和方向(支配树or后序支配树) */
      
      di.calc_dfs_tree ();                    /* DFS遍历当前函数中所有的结点,结果都存在这个临时的dom_info di中, 根据Lengauer-Tarjan 算法,快速计算必经节点的方法中首先就是要构建DFS */
      
      di.calc_idoms ();                       /* 按照LT算法, 计算出当前函数所有bb的直接支配结点,并将其保存在di.m_dom数组中 */

      FOR_EACH_BB_FN (b, cfun)                /* 遍历当前函数的所有bb, 将计算出的支配树信息转换为一个个支配结点的形式保存到当前函数的各个bb->dom中 */
      {
        if (basic_block d = di.get_idom (b))  /* 从dom_info di中获取基本块b的直接支配节点 d */
           et_set_father (b->dom[dir_index], d->dom[dir_index]);    /* 将idom(b) = d 这个信息记录到b/d两个基本块的 ->dom中(分别更新了d->dom[0]->son/ b->dom[0]->father等信息) */
      }
      dom_computed[dir_index] = DOM_NO_FAST_QUERY;    /* 标记当前函数的支配树状态为尚未开启快速查询 */
    }
  else                                        /* 若当前函数的支配树已经计算,只是尚未建立快速查询,则这里也是要重算并验证一遍支配树是否正确 */
    checking_verify_dominators (dir);         /* 重算并验证的流程实际上和if中的流程十分类似,只是最后的填充bb->dom变为了对比是否正确 */

  compute_dom_fast_query (dir);               /* 建立支配树的快速查询,实际上只是修改了各个bb->dom的 dfs_num_in/out字段, 以便于快速查找当前bb在dfs中包含的子结点编号 */
}

4. gcc中支配结点边界的计算

    在gcc中支配结点边界的信息是通过compute_dominance_frontiers函数计算的,其定义如下:

void compute_dominance_frontiers (bitmap_head *frontiers)
{
  timevar_push (TV_DOM_FRONTIERS);
  compute_dominance_frontiers_1 (frontiers);
  timevar_pop (TV_DOM_FRONTIERS);
}

    其具体实现就不展开了,此函数最关键的就是需要知道其返回的是一个n*n的二维数组,记录在frontiers指向的一个bitmap中, n为当前函数中基本块的个数, frontiers[x][y] = 1; 则代表对于基本块 x, 基本块y是其支配结点边界中的一个结点;


参考资料:

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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值