目录
1. GIMPLE生成
2. GIMPLE查看
3. GIMPLE遍历
4. GIMPLE中的全局变量和局部变量
5. GIMPLE pass的添加
前言
GIMPLE是从AST/GENERIC转换而来的三地址表示形式,它是一种与前端语言无关的中间表示,引入了临时变量来保存中间值。GIMPLE的生成分为高级GIMPLE(High-Level GIMPLE)和低级GIMPLE(Low-Level GIMPLE)两个阶段。
AST/GENERIC为树形结构,其节点属性较多,包含详细的功能信息,但由于其与前端语言相关缺乏通用性、结构复杂不是线性结构,不适合进行优化。GIMPLE降低控制流复杂度、三地址表示、限制语法,所以使优化变得相对容易。
在低级GIMPLE中,所有的GIMPLE_BIND被移除;多个GIMPLE_RETURN语句被合并;所有的if分支被转化成then和else两个分支;GIMPLE_TRY和GIMPLE_CATCH被转化为异常控制流。具体操作可在lower_function_body函数中查看。
1. GIMPLE生成
对于C语言AST/GENERIC到GIMPLE的转换在gcc/c-gimplify.c中以函数为单位进行。GIMPLE生成的入口函数为gimplify_function_tree
,在gimplify.c文件中,源码如下:
void gimplify_function_tree (tree fndecl)
{
tree parm, ret;
gimple_seq seq;
gbind *bind;
gcc_assert (!gimple_body (fndecl));
bind = gimplify_body (fndecl, true);
/* 中间代码 */
seq = NULL;
gimple_seq_add_stmt (&seq, bind);
gimple_set_body (fndecl, seq);
/* 中间代码 */
DECL_SAVED_TREE (fndecl) = NULL_TREE;
cfun->curr_properties |= PROP_gimple_any;
pop_cfun ();
dump_function (TDI_gimple, fndecl);
}
其中调用的gimplify_body
函数源码如下:
gbind * gimplify_body (tree fndecl, bool do_parms)
{
location_t saved_location = input_location;
gimple_seq parm_stmts, parm_cleanup = NULL, seq;
gimple *outer_stmt;
gbind *outer_bind;
struct cgraph_node *cgn;
/* 中间代码 */
parm_stmts = do_parms ? gimplify_parameters (&parm_cleanup) : NULL;
seq = NULL;
gimplify_stmt (&DECL_SAVED_TREE (fndecl), &seq);
outer_stmt = gimple_seq_first_stmt (seq);
GIMPLE的生成主要在gimplify_body
函数中,其内部代码主要调用gimplify_parameters
函数进行参数处理,gimplify_stmt
函数进行语句处理。
在此以gcc-8.2.0版本进行操作。test.c测试代码如下:
#include <stdio.h>
void combine(int argc)
{
if(argc == 1){ printf("argc value is 1\n"); }
}
int main(int argc, char **argv)
{
combine(argc);
printf("hello world");
return 0;
}
在C/C++语言中可以使用-fdump-tree-gimple
命令生成对应的GIMPLE文件,如下:
combine (int argc)
{
if (argc == 1) goto <D.2429>; else goto <D.2430>;
<D.2429>:
__builtin_puts (&"argc value is 1"[0]);
<D.2430>:
}
main (int argc, char * * argv)
{
int D.2431;
{
combine (argc);
printf ("hello world");
D.2431 = 0;
return D.2431;
}
D.2431 = 0;
return D.2431;
}
也可以使用-fdump-tree-all
或-fdump-tree-all-raw
命令生成中间操作的所有文件:
test.c源码经过前端解析生成的AST信息在test.c.003t.original
文件中,若想查看AST的详细信息,可使用gcc test.c -fdump-tree-original-raw
命令,此时生成的AST如下:
2. GIMPLE查看
对于GIMPLE生成时使用的两个命令-fdump-tree-all
和-fdump-tree-all-raw
在test.c.004t.gimple文件中区别如下:
以上为源码的高级GIMPLE,源码的低级GIMPLE内容如下:
低级GIMPLE的pass执行函数为lower_function_body
,在gimple-low.c文件中,源码如下:
static unsigned int
lower_function_body (void)
{
struct lower_data data;
gimple_seq body = gimple_body (current_function_decl);
gimple_seq lowered_body;
gimple_stmt_iterator i;
gimple *bind;
gimple *x;
/* 中间代码 */
bind = gimple_seq_first_stmt (body);
lowered_body = NULL;
gimple_seq_add_stmt (&lowered_body, bind);
i = gsi_start (lowered_body);
lower_gimple_bind (&i, &data);
i = gsi_last (lowered_body);
/* 中间代码 */
clear_block_marks (data.block);
data.return_statements.release ();
return 0;
通过gdb打印出的bt信息如下:
3. GIMPLE遍历
一个基本块(basic block)是包含GIMPLE语句的双链表。语句用GIMPLE元组表示,操作数用树数据结构表示。
- 在
pass pass_build_cfg
之前遍历gimple的代码如下:
/* GIMPLE statement iterator */
gimple_stmt_iterator gsi, seqi;
/* 获取当前函数的gimple序列 :current_function_decl等价于 cfun */
gimple_seq body = gimple_body (current_function_decl);
/* 循环遍历直到最后 : 向前迭代*/
for (gsi = gsi_start (body); !gsi_end_p (gsi); gsi_next (&gsi))
// for (gsi = gsi_last (seq); !gsi_end_p (gsi); gsi_prev (&gsi)) 向后迭代
{
/* 得到gimple */
gimple *g = gsi_stmt(gsi);
/* 获取最后一句gimple */
seqi = gsi_last (body);
...
}
- 在
pass pass_build_cfg
之后遍历gimple的示例代码如下:
gimple_seq body = gimple_body (current_function_decl);
basic_block bb;
gimple_stmt_iterator gsi, seqi;
/* cfun表示的当前函数的基本块迭代器 */
FOR_EACH_BB_FN (bb, cfun)
{
/* 打印bb块的后继 */
fprintf(dump file, " Successors of basic block bb: %d: \n", bb->index);
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
{
seqi = gsi_last (body);
gimple *stmt;
stmt = gsi_stmt(gsi);
switch(gimple_code(stmt))
{
case GIMPLE_COND:
printf("GIMPLE_COND \n");
break;
case GIMPLE_GOTO:
printf("GIMPLE_GOTO \n");
break;
case GIMPLE_LABEL:
printf("GIMPLE_LABEL \n");
break;
case GIMPLE_SWITCH:
printf("GIMPLE_SWITCH \n");
break;
case GIMPLE_CALL:
printf("GIMPLE_CALL \n");
break;
case GIMPLE_BIND:
printf("GIMPLE_BIND \n");
break;
}
}
}
由以上代码可以看出遍历操作是有差别的,因为bb块在构建cfg时才会生成,若在pass pass_build_cfg
之前采用gsi_start_bb
遍历是进入不了循环的。
- gimple edge的遍历示例如下:
edge e;
edge_iterator ei;
basic block bb;
FOR_EACH_BB_FN (bb, cfun)
{
fprintf(dump file, " Successors of basic block bb: %d: \n", bb->index);
FOR_EACH_EDGE (e, ei, bb->succs)
{
basic_block success_bb = e->dest;
fprintf(dump file, "bb : %d\t ", success_bb->index);
}
fprintf(dump file, " end \n");
}
- 常用的迭代方法还有以下一些
- bool gsi_end_p (gimple_stmt_iterator i):判断是否到结尾
- bool gsi_one_before_end_p (gimple_stmt_iterator i):判断一条语句是否在结尾语句前
- void gsi_next (gimple_stmt_iterator *i):下一句GIMPLE
- void gsi_prev (gimple_stmt_iterator *i):前一句GIMPLE
- gimple_stmt_iterator gsi_after_labels (basic_block BB):返回一个指向BB中第一个非标签语句的block语句迭代器
- gimple * gsi_stmt_ptr (gimple_stmt_iterator * i):返回指向当前stmt的指针
- basic_block gsi_bb (gimple_stmt_iterator i):返回与此迭代器关联的基本块
- gimple_seq gsi_seq (gimple_stmt_iteratorⅰ):返回与此迭代器关联的序列
- void gsi_insert_before (gimple_stmt_iterator *i, gimple stmt, enum gsi_iterator_update mode)、void gsi_insert_seq_before (gimple_stmt_iterator *i, gimple_seq seq, enum gsi_iterator_update mode)两个函数都是在位置之前插入gimple语句。位置之后插入gimple语句则把before改为after即可
更多关于GIMPLE的操作方法请见:https://gcc.gnu.org/onlinedocs/gccint/GIMPLE-sequences.html#GIMPLE-sequences
4. GIMPLE中的全局变量和局部变量
GCC中关于局部变量的操作定义在function.h中,源码如下:
#define FOR_EACH_LOCAL_DECL(FUN, I, D) \
FOR_EACH_VEC_SAFE_ELT_REVERSE ((FUN)->local_decls, I, D)
使用示例如下:
tree var;
unsigned i;
if (!dump_file)
return;
fprintf(dump_file,"Local variables : \n");
FOR_EACH_LOCAL_DECL (cfun, i, var)
{
/* 排除未出现在源代码中的变量 */
if (!DECL_ARTIFICIAL (var))
fprintf(dump_file, "%s\n", get_name (var));
}
全局变量操作示例如下:
struct varpool_node *node;
if (!dump_file)
return;
fprintf(dump_file,"Global variables : \n");
for (node = varpool_nodes; node; node = node->next)
{
tree var = node->decl;
if (!DECL_ARTIFICIAL(var))
{
fprintf(dump_file, "%s\n", get_name (var));
}
}
5. GIMPLE pass的添加
在gcc-8.2.0中添加GIMPLE pass,自己在添加的时候参照了参考了一些文档和国外论坛,但遇到很多问题,最终参考了:https://blog.csdn.net/qiusi0225/article/details/103985963 这篇博客解决了问题。在此我还是整理了一下我的操作步骤。
步骤如下:
- 在gcc-8.2.0/gcc/下新建test_pass.c文件
/* 自定义执行函数 */
static unsigned int
execute_XXX_function (void)
{
// TODO .....
return 0;
}
namespace {
const pass_data pass_data_bf =
{
GIMPLE_PASS, /* type */
"pass_bf", /* name */ // 名称自己随便取
OPTGROUP_NONE, /* optinfo_flags */
TV_GIMPLE_BF, /* tv_id */ // 1
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
// 2
class pass_bf : public gimple_opt_pass
{
public:
/* 注意:构造函数名称;参数名称对应 */
pass_bf (gcc::context *ctxt)
: gimple_opt_pass (pass_data_bf, ctxt)
{}
/* opt_pass methods: */
/* 注意:此处函数名称 */
virtual unsigned int execute (function *) { return execute_XXX_function (); }
}; // class pass_build_cfg
} // anon namespace
// 3
gimple_opt_pass *
make_pass_bf (gcc::context *ctxt)
{
/* 注意:与class名称 */
return new pass_bf(ctxt);
}
- 在timevar.def文件中末尾添加tv_id:
// 与第一步中 1 的名称对应
DEFTIMEVAR (TV_GIMPLE_BF , "load gimple bf pass")
- 在passes.def中添加一个宏:
// 与第一步中 2 的名称对应
NEXT_PASS (pass_bf);
- 在tree-pass.h中添加代码:
// 与第一步中 3 的名称对应
extern gimple_opt_pass *make_pass_bf (gcc::context *ctxt);
- Makefile.in中添加test_pass.c以及test_pass.o
// 注意文件名称
...
tree-call-cdce.o \
tree-cfg.o \
...
$(srcdir)/gimple.h \
$(srcdir)/gimple-ssa.h \
$(srcdir)/test_pass.c \
...
- 对GCC进行
../configure、make、make install
之后可fdump-tree-all
测试该pass是否能成功打印或gdb调试。
References:
- 《深入分析GCC》王亚刚.编著
- https://gcc.gnu.org/onlinedocs/gccint/GIMPLE.html#GIMPLE
- https://www.cse.iitb.ac.in/grc/gcc-workshop-13/downloads/slides/Day1/gccw13-gimple-manipulation.pdf
- https://blog.csdn.net/qiusi0225/article/details/103985963