gcc中的SSA化算法

SSA化算法过程


SSA化算法过程

gcc中SSA化的算法描述

SSA化的流程可以简化为:

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

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

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

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

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

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

  2) 再通过compute_dominance_frontiers计算当前函数的支配结点边界信息
遍历并记录函数每条指令中出现的变量的defs/uses信息

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

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

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

1
针对当前函数中的每个变量均需要通过此算法计算出其需要插入phi函数的结点信息,并在对应结点为每个变量分别插入phi函数
按照支配树的DFS遍历重写每个bb的每条指令并将整个函数SSA化

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

1
2
3
4
5
6
7
8
9
(1)对于每个结点顺序需遍历所有语句,对于每条语句:

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

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

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

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

gcc中ssa化的整体流程

pass_build_ssa

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
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;
}
函数指令序列的def/use分析

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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,此函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);
}
为每个变量插入phi函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* 此函数负责为当前函数中所有变量在需要插入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);
}
}
指令重写(SSA化)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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);
}
}
}

SSA基本概念及原理参考文章

原文链接

-------------本文结束 感谢阅读-------------
献上你的银子!

欢迎关注我的其它发布渠道