您的位置:首页 > 其它

GCC源码分析(5):自动向量化遍

2014-10-30 23:00 246 查看
转自:http://blog.chinaunix.net/uid-13800995-id-67986.html

GCC中的自动向量化

在前面,我们已经分析过了GCC的Tree-SSA优化框架。这篇文章分析这个框架下的一个

优化——自动向量化。

打开passes.c文件后,在init_optimization_passes函数中,可以看到和向量化有关

的几个遍:all_lowering_passes中的pass_lower_vector(定义在tree-vect-generic.c

中),all_passes->pass_all_optimizations->pass_tree_loop->pass_vectorize(定义在

tree-ssa-loop.c中)遍及其子遍pass_lower_vector_ssa(定义在tree-vect-generic.c中

)和pass_dce_loop(定义在tree-ssa-dce.c中)。感觉上,pass_vectorize应该是主要的

优化遍,而其它几遍是辅助遍,所以,下面从pass_vectorize开始看起。

文件tree-ssa-loop.c中定义的是一些在Tree-SSA中间表示上对循环进行的优化的相关

工作。我只关心自动向量化这一遍。

{code}

struct tree_opt_pass pass_vectorize =

{

"vect", /* 遍名字 */

gate_tree_vectorize, /* 是否执行该遍的谓词 */

tree_vectorize, /* 该遍执行的函数 */

NULL, /* 用于遍的组织 */

NULL, /* 用于遍的组织 */

0, /* 遍号 */

TV_TREE_VECTORIZATION, /* 时间统计变量号 */

PROP_cfg | PROP_ssa, /* 该遍执行前需要cfg存在,并且程序处于

ssa表示中 */

0, /* 该遍不提供任何新属性 */

0, /* 该遍保持所有属性 */

TODO_verify_loops, /* 开始时需要做的事情 */

TODO_dump_func | TODO_update_ssa, /* 结束时需要做的事情 */

0 /* 导出相关的字母 */

};

/* gate函数非常简单,只要flag_tree_vectorize设置,并且当前的循环

不为空,就做自动向量化 */

static bool

gate_tree_vectorize (void)

{

return flag_tree_vectorize && current_loops;

}

/* 向量化的主函数直接调用vectorize_loops(定义在文件tree-vectorizer.c中)函数,然

后返回0. */

static unsigned int

tree_vectorize (void)

{

vectorize_loops (current_loops);

return 0;

}

{/code}

好了,我们已经看到自动向量化遍的入口了,之后就是对自动向量化的详细分析。这里的分析基于GCC4.2.2,因为最近做的事情是基于这个版本的。

在分析vectorize_loops函数之前,我们先看看几个相关的数据结构。

在GCC的Tree-SSA优化框架中,循环信息主要是由两个结构体表示的:struct loop和

struct loops. 前者保存一个自然循环的信息,而后者则用于组织一个函数内的所有自然

循环的信息。

{code}

/* 保存一个自然循环信息的结构体 */

struct loop

{

/* 在loops的数组中的下标 */

int num;

/* 循环头基本块 */

basic_block header;

/* Basic block of loop latch. 这个是什么? */

basic_block latch;

/* 关于循环展开和剥离的信息,主要有两个成员,一个是展开的决定,一个是展开的次

数 */

struct lpt_decision lpt_decision;

/* 循环中的指令数 */

unsigned ninsns;

/* 每次迭代执行的平均指令数 */

unsigned av_ninsns;

/* 基本块数 */

unsigned num_nodes;

/* 循环嵌套深度 */

int depth;

/* Superloops of the loop. 这是什么? */

struct loop **pred;

/* 在循环构成的树中的高度,它包含的循环的层数 */

int level;

/* 该循环的外层循环 */

struct loop *outer;

/* 第一个包含在该循环中的循环 */

struct loop *inner;

/* 与该循环同深度的下一个兄弟循环 */

struct loop *next;

/* 是该循环拷贝的循环 */

struct loop *copy;

/* 辅助信息,不同的遍可以设置不同的东西 */

void *aux;

/* 运行时可能执行的迭代次数,是一个INTEGER_CST或者一个包括符号名字的表达式。

一般不直接访问这个域,使用

number_of_iterations_in_loop */

tree nb_iterations;

/* 一个INTEGER_CST表示迭代次数的估计. NULL_TREE表示没有估计 */

tree estimated_nb_iterations;

/* 循环迭代的上界 */

struct nb_iter_bound *bounds;

/* 非空表示该循环只有一个出口,出口边存储在这里 (指向EXIT_BLOCK_PTR的边不算*/

edge single_exit;

/* “真”表示该循环没有loop carried数据依赖,所有的迭代可以以任意顺序执行

“假”表示可能存在 loop carried数据依赖 */

bool parallel_p;

};

{/code}

{code}

/* 组织一个函数中所有自然循环的结构体 */

struct loops

{

/* 循环的数目 */

unsigned num;

/* 循环的状态 */

int state;

/* 该数组存储了指向循环的指针,数组元素可能为NULL。这是因为循环已经被删除,但

是该结构还没有被更新。*/

struct loop **parray;

/* 指向循环层次树根的指针 */

struct loop *tree_root;

/* 从CFG得到的信息 */

struct cfg

{

/* 深度优先搜索的基本块序 */

int *dfs_order;

/* The reverse completion ordering of the basic blocks found in a

depth first search. 这是什么? */

int *rc_order;

} cfg;

/* 多个应该归并的循环共享的循环头 */

sbitmap shared_headers;

};

{/code}

loop_vec_info类型是自动向量化附加在循环上面的主要信息。自动向量化的多个步骤

之间使用这个结构体传递信息。

{code}

/* 自动向量化相关的信息 */

typedef struct _loop_vec_info {

/* 该信息关联的循环 */

struct loop *loop;

/* 循环的基本块 */

basic_block *bbs;

/* 循环退出条件 */

tree exit_cond;

/* 循环的迭代数 */

tree num_iters;

/* 该循环是否可以被向量化 */

bool vectorizable;

/* 向量化因子/循环展开因子 */

int vectorization_factor;

/* 可能的非对齐的数据引用,根据这个进行循环剥离 */

struct data_reference *unaligned_dr;

/* 为数据对齐而进行的循环剥离:

peeling_for_alignment = X 意味着:

If X=0: 不做循环剥离;

If X>0: 剥离头X个迭代.

If X=-1: 生成运行时的测试来决定循环剥离的数目,使用unaligned_dr中的信息

*/

int peeling_for_alignment;

/* 检查指针或数组是否对齐的掩码 */

int ptr_mask;

/* 循环中所有的数据引用 */

VEC (data_reference_p, heap) *datarefs;

/* 循环中所有的数据依赖 */

VEC (ddr_p, heap) *ddrs;

/* 这里面的语句包含的数据引用是在运行时进行对齐检查(Loop Versioning)的候选者

*/

VEC(tree,heap) *may_misalign_stmts;

/* 源代码中的循环的位置 */

LOC loop_line_number;

} *loop_vec_info;

{/code}

好的,现在我们就可以读读循环自动向量化的控制框架了。

{code}

/* 这个函数是循环自动向量化的主入口,也是自动向量化的控制框架 */

void

vectorize_loops (struct loops *loops)

{

unsigned int i;

unsigned int num_vectorized_loops = 0;

/* 该函数设置dump信息相关的变量,包括verbose级别 */

vect_set_dump_settings ();

/* 分配位图来记录需要被重命名的虚变量, 这个位图是全局变量 */

vect_vnames_to_rename = BITMAP_ALLOC (NULL);

/* ----------- 向量化的主框架 ----------- */

/* 如果在该过程中新生成了一些循环,那么这些新循环的下标比已有循环的都大。记录

当前的循环数,使得我们只在当前已有的循环上面做变换,而不去考虑在变换中新生成的

循环, 该变量也是全局的。 */

vect_loops_num = loops->num;

for (i = 1; i < vect_loops_num; i++) /* 为什么从1开始? */

{

loop_vec_info loop_vinfo;

struct loop *loop = loops->parray[i];

if (!loop) /* 跳过parray中空的指针 */

continue;

/* 全局变量,记录当前循环的源代码位置 */

vect_loop_location = find_loop_location (loop);

/* 分析当前循环,将分析结果记录在loop_vinfo指向的结构中,并使得该循环

的辅助信息也指向该结构,以备后用 */

loop_vinfo = vect_analyze_loop (loop);

loop->aux = loop_vinfo;

/* 如果分析返回的

* loop_vinfo为空,或者loop_vinfo中的是否可向量化的标志为假,则

* 该循环不能向量化,继续考察下一个循环 */

if (!loop_vinfo || !LOOP_VINFO_VECTORIZABLE_P (loop_vinfo))

continue;

/* 向量化当前循环 */

vect_transform_loop (loop_vinfo, loops);

num_vectorized_loops++;

}

vect_loop_location = UNKNOWN_LOC;

/* 打印必要的导出信息 */

if (vect_print_dump_info (REPORT_VECTORIZED_LOOPS))

fprintf (vect_dump, "vectorized %u loops in function.\n",

num_vectorized_loops);

/* ---------- 析构自动向量化的数据结构 ----------- */

BITMAP_FREE (vect_vnames_to_rename);

for (i = 1; i < vect_loops_num; i++)

{

struct loop *loop = loops->parray[i];

loop_vec_info loop_vinfo;

if (!loop)

continue;

loop_vinfo = loop->aux;

destroy_loop_vec_info (loop_vinfo);

loop->aux = NULL;

}

}

{/code}

由上面的代码可以看到,自动向量化会按照current_loops中的数组的顺序,依次分析

、向量化每个循环。最重要的函数就是vect_analyze_loop和vect_transform_loop。前者对

一个循环做各种分析,以确定该循环能否进行向量化以及向量化的方式,并将所有的信息记

录在一个loop_vec_info指向的结构体中,而后者则根据这里面的信息做实际的变换,以向

量化目标循环。

下面,我们继续来看vect_anaylyze_loop这个函数,它定义在文件

tree-vect-analyze.c中。事实上,该函数也是一个框架函数,它组织了各种不同的分析。

它本身就是输出一些信息,并组织这些具体的分析函数。

1. vect_anaylyze_loop_form

检查LOOP在CFG方面的特点,包括嵌套层次,出口,入口情况等。有以下限制:

a. 最内层循环

b. 基本块数为2,分别是循环头和latch

c. 循环有pre-header

d. 循环有单一的入口和出口

e. 循环的退出条件必须足够简单,并且该循环的迭代数可以被分析(countable loop).

2. vect_anaylyze_data_ref

找到循环中所有的数据引用(对应于vdefs/vuses)并分析它们在循环中的演化。当前

仅仅处理简单的,对齐可以强制保证的数组引用和对齐的指针引用。

3. vect_analyze_scalar_cycles

将loop carried标量数据依赖环分类,其中由于virtual phi引起的单独在其他地方分

析。类别包括:

a. reduction, 目前不支持

loop1:

for (i=0; i<N; i++)

sum += a[i];

b. induction, 目前不支持

loop2:

for (i=0; i<N; i++)

a[i] = i;

c. invariant

d. unknow

下面的循环是可以向量化的:

loop3:

for (i=0; i<N; i++)

a[i] = b[i];

因为由i引起的定义-使用环被认为是“不相关的”,i不需要被向量化,见

mark_stmts_to_be_vectorized.

4. vect_pattern_recog

寻找计算模式,对与每一个找到的支持的模式,插入一个新的可以被向量化的语句。

同时,也在struct_stmt_info中记录一些信息。其过程如下:

假设开始时我们有如下语句:

stmt in_pattern_p related_stmt vec_stmt

S1: a_i = .... - - -

S2: a_2 = ..use(a_i).. - - -

S3: a_1 = ..use(a_2).. - - -

S4: a_0 = ..use(a_1).. - - -

S5: ... = ..use(a_0).. - - -

并且,{S1,S2,S3,S4}被识别为一个模式,那么,建立一个新语句S6用于替代该模式,

将S6插入到模式的最后一条语句S4之前,如下填写相关域:

in_pattern_p related_stmt vec_stmt

S1: a_i = .... - - -

S2: a_2 = ..use(a_i).. - - -

S3: a_1 = ..use(a_2).. - - -

> S6: a_new = .... - S4 -

S4: a_0 = ..use(a_1).. true S6 -

S5: ... = ..use(a_0).. - - -

在vect_mask_stmts_to_be_vectorized中,S6会被标记为相关而S4不会,因为所有使用

S4结果的地方,都被S6的结果替换。{S1,S2,S3}仍然会保持不相关,除非会被其它向量

语句使用。

如果向量化成功,vect_transform_stmt会跳过{S1,S2,S3}(如果它们还是不相关的),

向量化S6,并且在S6和S4中记录向量化的新的VS6语句。这样做是为了当我们需要向量

化使用S4的结果的语句时,可以知道从什么地方找。S4被跳过,S5照常向量化。

in_pattern_p related_stmt vec_stmt

S1: a_i = .... - - -

S2: a_2 = ..use(a_i).. - - -

S3: a_1 = ..use(a_2).. - - -

> VS6: va_new = .... - - -

S6: a_new = .... - S4 VS6

S4: a_0 = ..use(a_1).. true S6 VS6

> VS5: ... = ..vuse(va_new).. - - -

S5: ... = ..use(a_0).. - - -

之后的DCE遍无用的语句{S1,S2,S3,S4,S5,S6}:

VS6: va_new = ....

VS5: ... = ..vuse(va_new)..

如果向量化没有成功,那么DCE遍会删除S6(它的结果没人使用),得到原来的序列。

5. vect_mask_stmts_to_be_vectorized

数据流分析,确定哪些语句不需要被向量化。并不是一个循环中所有的语句都需要向量

化,例如

for i...

for j...

1. T0 = i + j

2. T1 = a[T0]

3. j = j + 1

中语句1和3就不需要向量化,循环控制和地址计算是不需要向量化的。

6. vect_analyze_data_refs_alignment

分析循环中数据引用的对齐.

7. vect_determine_vectorization_factor

当前支持的循环中需要向量化的数据类型必须是相同的,根据这个类型来确定VF。

8. vect_analyze_data_ref_dependences

分析循环中数据引用之间的依赖。目前支持的必须是语句之间没有任何数据依赖的。

9. vect_analyze_data_ref_accesses

分析循环中数据引用的访问模式,目前只支持连续访问。目前仅支持数组和指针访问。

10. vect_enhance_data_refs_alignment

该分析确定是否使用loop versioning或者循环剥离来达到循环中数据引用的对齐。

当前限制无论是loop versioning还是循环剥离,仅仅在原循环中向量化,新生成的循

环不进行向量化。这一分析需要一个代价模型。

11. vect_analyze_operations

分析循环中语句的操作是否是可以被向量化的操作。

如果上面的任何一个分析失败,则销毁已经分配的loop_vec_info结构,并返回NULL,

否则,设置loop_vec_info中的vectorizable为1,返回该结构。

函数vect_transform_loop则完成实际的向量语句生成、修改循环退出条件等等向量化

的工作。这个函数之前已经由分析确定当前循环可以被向量化,所以该函数不会失败。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: