Next: , Previous: Annotations, Up: Tree SSA


13.2 SSA操作数

几乎每条GIMPLE语句都会包含对变量或者内存地址的引用。由于语句的形状和大小不同, 它们的操作数也将会位于语句树中的不同位置。为了便于访问语句的操作数, 它们被组织到与语句的注解(annotation)相关联的一个列表中。 操作数列表中的每个元素都是一个指向VAR_DECL, PARM_DECLSSA_NAME树结点的指针。这就为检查和替换操作数提供了一种非常方便的方法。

数据流分析和优化是在所有表示变量的树结点上完成的。扫描语句操作数时, 将会考虑所有SSA_VAR_P返回非零的节点。但是, 并不是所有的SSA_VAR_P变量都使用同一种方式来处理。出于优化的目的, 我们需要区分对局部标量的引用和对全局变量,静态变量,结构体,数组,别名变量的引用,等等。 原因很简单,一方面,编译器能够为局部标量收集完整的数据流信息;另一方面, 全局变量可能会被函数调用所修改, 另外,可能无法追踪数组所有的元素或结构体所有的域的信息,等等。

操作数扫描器搜集两类操作数:实的(real)和虚的(virtual)。 is_gimple_reg返回真的操作数被认为是实操作数,否则为一个虚操作数。 我们还区分了它们的使用和定义。如果操作数的值被语句加载(例如,操作数在赋值的右边), 则为使用。如果语句给操作数赋于了一个新的值(例如,操作数在赋值语句的左边),则为定义。

虚操作数和实操作数还具有不同的数据流属性。 实操作数是对它们表示的完整对象的明确引用。例如,给定

     {
       int a, b;
       a = b
     }

由于ab为非别名的局部变量, 语句a = b将具有一个实定义和一个实使用, 因为变量a完全被变量b的内容修改了。 实定义还被称作为killing definition(杀死定义)。 类似的,对b的使用是读取了它的所有位。

与此相反,虚操作数用于具有部分或者不明确引用的变量。这包括结构体,数组,全局变量和别名变量。 这些情况下,我们具有两种类型的定义。对于全局变量,结构体和数组, 我们能够从语句中确定这些类型的变量是否具有一个killing definition(杀死定义)。如果具有, 则语句被标记为具有那个变量的必然定义(must definition)。但是, 如果语句只是定义了变量的一部分(即,结构体中的一个域), 或者如果我们知道语句可能会定义变量,但是不确定, 则我们将那条语句标记为具有一个可能定义(may definition)。例如,给定

     {
       int a, b, *p;
     
       if (...)
         p = &a;
       else
         p = &b;
       *p = 5;
       return *p;
     }

赋值*p = 5可能为a或者b的定义。 如果我们不能静态地确定在存储操作的时候p的指向, 我们便创建一个虚定义来标记那条语句为一个ab的潜在的定义。 内存加载也类似的使用虚操作数进行标记。 虚操作数在树转储(dump)中显示在包含它们的语句前面。 要获得带有虚操作数的树转储,使用-fdump-tree-vops选项:

     {
       int a, b, *p;
     
       if (...)
         p = &a;
       else
         p = &b;
       # a = VDEF <a>
       # b = VDEF <b>
       *p = 5;
     
       # VUSE <a>
       # VUSE <b>
       return *p;
     }

注意VDEF操作数具有被引用变量的两个副本。 这表明不是一个那个变量的killing definition(杀死定义)。在这种情况下, 我们称它为一个可能定义(may definition)或者 别名存储(aliased store)。 当函数被转换为SSA形式的时候,VDEF操作数的第二个变量副本将会变得很重要。 其将用于链接所有的非killing definition(杀死定义),用来防止优化对它们做错误的假设。

当语句完成时,便会立刻通过调用update_stmt来更新操作数。 如果语句元素通过SET_USESET_DEF被改变, 则不需要进一步的动作(即,那些宏会处理好语句更新)。 如果改变是通过直接操作语句的树,则必须在完成时调用update_stmt。 调用bsi_insert例程中的任何一个,或者bsi_replace, 都会隐式的调用update_stmt

13.2.1 操作数迭代器和访问例程

与操作数相关的代码都在tree-ssa-operands.c中。 操作数被存储在每条语句的注解中并且可以通过操作数迭代器或者访问例程来访问。

下列访问例程可以用来检查操作数:

    这些访问例程将会返回NULL,除非恰好有一个操作数匹配指定的标志。 如果恰好存在一个操作数,则操作数被作为tree,def_operand_p或者 use_operand_p返回。
              tree t = SINGLE_SSA_TREE_OPERAND (stmt, flags);
              use_operand_p u = SINGLE_SSA_USE_OPERAND (stmt, SSA_ALL_VIRTUAL_USES);
              def_operand_p d = SINGLE_SSA_DEF_OPERAND (stmt, SSA_OP_ALL_DEFS);
    
  1. ZERO_SSA_OPERANDS: 该宏返回真,如果没有操作数匹配指定的标志。
              if (ZERO_SSA_OPERANDS (stmt, SSA_OP_ALL_VIRTUALS))
                return;
    
  2. NUM_SSA_OPERANDS: 该宏返回匹配'flags'的操作数数目。其实际上是执行了一个循环来进行统计, 所以最好只有在真正需要的时候才使用它。
              int count = NUM_SSA_OPERANDS (stmt, flags)
    

如果你想迭代一些或者所有操作数, 使用FOR_EACH_SSA_{USE,DEF,TREE}_OPERAND迭代器。 例如,要打印语句的所有操作数:

     void
     print_ops (tree stmt)
     {
       ssa_op_iter;
       tree var;
     
       FOR_EACH_SSA_TREE_OPERAND (var, stmt, iter, SSA_OP_ALL_OPERANDS)
         print_generic_expr (stderr, var, TDF_SLIM);
     }

如何选择合适的迭代器:

  1. 确定你是否需要看到操作数指针,或者只是树,并选择合适的宏
              Need            Macro:
              ----            -------
              use_operand_p   FOR_EACH_SSA_USE_OPERAND
              def_operand_p   FOR_EACH_SSA_DEF_OPERAND
              tree            FOR_EACH_SSA_TREE_OPERAND
    
  2. 你需要声明一个你感兴趣的类型的变量,和一个用作循环控制变量的ssa_op_iter结构体
  3. 确定你想使用哪些操作数,并指定你所感兴趣的那些操作数的标志。 这些标志在tree-ssa-operands.h中声明:
              #define SSA_OP_USE              0x01    /* Real USE operands.  */
              #define SSA_OP_DEF              0x02    /* Real DEF operands.  */
              #define SSA_OP_VUSE             0x04    /* VUSE operands.  */
              #define SSA_OP_VMAYUSE          0x08    /* USE portion of VDEFS.  */
              #define SSA_OP_VDEF             0x10    /* DEF portion of VDEFS.  */
              
              /* These are commonly grouped operand flags.  */
              #define SSA_OP_VIRTUAL_USES     (SSA_OP_VUSE | SSA_OP_VMAYUSE)
              #define SSA_OP_VIRTUAL_DEFS     (SSA_OP_VDEF)
              #define SSA_OP_ALL_USES         (SSA_OP_VIRTUAL_USES | SSA_OP_USE)
              #define SSA_OP_ALL_DEFS         (SSA_OP_VIRTUAL_DEFS | SSA_OP_DEF)
              #define SSA_OP_ALL_OPERANDS     (SSA_OP_ALL_USES | SSA_OP_ALL_DEFS)
    

所以,如果你想查看所有USEVUSE操作数的use指针, 则可以使用类似下面的方法:

       use_operand_p use_p;
       ssa_op_iter iter;
     
       FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, (SSA_OP_USE | SSA_OP_VUSE))
         {
           process_use_ptr (use_p);
         }

TREE基本上与宏USEDEF相同, 除了通过USE_FROM_PTR (use_p)DEF_FROM_PTR (def_p)进行的 use或def的解引用。因为我们不会使用操作数指针,所以可以混合use和def标志。

       tree var;
       ssa_op_iter iter;
     
       FOR_EACH_SSA_TREE_OPERAND (var, stmt, iter, SSA_OP_VUSE)
         {
            print_generic_expr (stderr, var, TDF_SLIM);
         }

VDEF被分解为两个标记,一个是DEF部分(SSA_OP_VDEF), 一个是USE部分(SSA_OP_VMAYUSE)。 如果你只是想要查看合在一起的VDEF,则可以使用第四个迭代器, 其返回语句中每个VDEF的 def_operand_p和use_operand_p。 注意该宏不需要任何标记。

       use_operand_p use_p;
       def_operand_p def_p;
       ssa_op_iter iter;
     
       FOR_EACH_SSA_MAYDEF_OPERAND (def_p, use_p, stmt, iter)
         {
           my_code;
         }

代码中也有很多例子,同时在tree-ssa-operands.h中也有文档。

还有一些stmt迭代器是用于处理PHI节点的。

FOR_EACH_PHI_ARGFOR_EACH_SSA_USE_OPERAND非常类似, 只不过它是工作于PHI参数,而不是语句操作数。

     /* Look at every virtual PHI use.  */
     FOR_EACH_PHI_ARG (use_p, phi_stmt, iter, SSA_OP_VIRTUAL_USES)
     {
        my_code;
     }
     
     /* Look at every real PHI use.  */
     FOR_EACH_PHI_ARG (use_p, phi_stmt, iter, SSA_OP_USES)
       my_code;
     
     /* Look at every PHI use.  */
     FOR_EACH_PHI_ARG (use_p, phi_stmt, iter, SSA_OP_ALL_USES)
       my_code;

FOR_EACH_PHI_OR_STMT_{USE,DEF}FOR_EACH_SSA_{USE,DEF}_OPERAND非常类似, 只不过它是作用于语句或者PHI节点。 这些宏可以在合适的时候使用,但是它们比单独使用FOR_EACH_PHIFOR_EACH_SSA例程的效率要低。

     FOR_EACH_PHI_OR_STMT_USE (use_operand_p, stmt, iter, flags)
       {
          my_code;
       }
     
     FOR_EACH_PHI_OR_STMT_DEF (def_operand_p, phi, iter, flags)
       {
          my_code;
       }

13.2.2 立即使用

现在immediate use(这个短语咋翻译?)信息总是可以被获得。 使用immediate use迭代器,你可以检查任意SSA_NAME的所有使用。 例如,要将ssa_var的所有使用改为ssa_var2, 并且之后在每个stmt上调用fold_stmt:

       use_operand_p imm_use_p;
       imm_use_iterator iterator;
       tree ssa_var, stmt;
     
     
       FOR_EACH_IMM_USE_STMT (stmt, iterator, ssa_var)
         {
           FOR_EACH_IMM_USE_ON_STMT (imm_use_p, iterator)
             SET_USE (imm_use_p, ssa_var_2);
           fold_stmt (stmt);
         }

这里有两个可以使用的迭代器。 FOR_EACH_IMM_USE_FAST用于当immediate use没有被改变的情况下,即, 只是进行查看use,但不设置它们。

如果确实要做改变,则必须要考虑到迭代器下没有被改变的事物,这时, 可以使用FOR_EACH_IMM_USE_STMTFOR_EACH_IMM_USE_ON_STMT迭代器。 它们试图通过将语句的所有use移动到一个被控制的位置并对它们进行迭代的方式, 来保证use列表的完整性。然后优化就能够在所有的use被处理完后来操作stmt。 这比FAST版本的有点慢,因为它增加了一个占位元素并且必须对每条语句的列表进行排序。 如果循环被提前终止,则该占位元素还必须被移除。 宏BREAK_FROM_IMM_USE_SAFE用于做这个:

       FOR_EACH_IMM_USE_STMT (stmt, iterator, ssa_var)
         {
           if (stmt == last_stmt)
             BREAK_FROM_SAFE_IMM_USE (iter);
     
           FOR_EACH_IMM_USE_ON_STMT (imm_use_p, iterator)
             SET_USE (imm_use_p, ssa_var_2);
           fold_stmt (stmt);
         }

verify_ssa中有一些检测用来验证immediate use列表是最新的, 同时还检测一个优化是否没有使用该宏而中断循环。 在FOR_EACH_IMM_USE_FAST遍历中,直接使用'break'语句是安全的。

一些有用的函数和宏:

  1. has_zero_uses (ssa_var) : 如果没有ssa_var的使用,则返回真。
  2. has_single_use (ssa_var) : 如果只有ssa_var的单个使用,则返回真。
  3. single_imm_use (ssa_var, use_operand_p *ptr, tree *stmt) : 如果只有ssa_var的单个使用,则返回真, 并且还在第二和第三个参数中返回使用指针和所在的语句。
  4. num_imm_uses (ssa_var) : 返回ssa_var的immediate use的数目。最好不要使用该宏, 因为它只是简单的使用循环来统计use。
  5. PHI_ARG_INDEX_FROM_USE (use_p) : 给定一个在PHI节点中的use,返回use的索引数。 如果use不位于PHI节点中,则会触发一个断言。
  6. USE_STMT (use_p) : 返回use所在的语句。

注意在语句通过bsi_*程序被实际插入指令流中之前, use是不被放入immediate use列表中的。

还可以使用懒散的语句更新方式,不过这应该在确实需要的时候才使用。 别名分析和dominator优化目前都采用了这种方式。

当使用懒散更新(lazy updating)时,immediate use信息是过时的,不能被信赖。 懒散更新简单的调用mark_stmt_modified来标记语句被修改了, 而不使用update_stmt。当不再需要进行懒散更新时, 所有修改的语句都必须调用update_stmt来保持更新。 这必须在优化完成之前进行,否则verify_ssa将触发abort 异常中断。

这是通过对指令流进行简单的循环来实现的:

       block_stmt_iterator bsi;
       basic_block bb;
       FOR_EACH_BB (bb)
         {
           for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi))
             update_stmt_if_modified (bsi_stmt (bsi));
         }