GCC - GIMPLE IR 学习一

目录

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命令生成中间操作的所有文件:
1

test.c源码经过前端解析生成的AST信息在test.c.003t.original文件中,若想查看AST的详细信息,可使用gcc test.c -fdump-tree-original-raw命令,此时生成的AST如下:
2


2. GIMPLE查看

对于GIMPLE生成时使用的两个命令-fdump-tree-all-fdump-tree-all-raw在test.c.004t.gimple文件中区别如下:
3
以上为源码的高级GIMPLE,源码的低级GIMPLE内容如下:
4

低级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信息如下:
5


3. GIMPLE遍历

一个基本块(basic block)是包含GIMPLE语句的双链表。语句用GIMPLE元组表示,操作数用树数据结构表示。

  1. 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);
    ...
}
  1. 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遍历是进入不了循环的。

  1. 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");
}
  1. 常用的迭代方法还有以下一些
  • 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 这篇博客解决了问题。在此我还是整理了一下我的操作步骤。
步骤如下:

  1. 在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);
}
  1. 在timevar.def文件中末尾添加tv_id:
// 与第一步中 1 的名称对应
DEFTIMEVAR (TV_GIMPLE_BF , "load gimple bf pass")
  1. 在passes.def中添加一个宏:
// 与第一步中 2 的名称对应
NEXT_PASS (pass_bf);
  1. 在tree-pass.h中添加代码:
// 与第一步中 3 的名称对应
extern gimple_opt_pass *make_pass_bf (gcc::context *ctxt);
  1. 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 \
...
  1. 对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
原创文章 38 获赞 13 访问量 4015

猜你喜欢

转载自blog.csdn.net/qq_36287943/article/details/105458166
gcc