GCC-3.4.6源代码学习笔记(8)

1.3. 为非一元表达式创建节点

函数build可被用于为表达式创建节点。但它只接受表达式作为参数,把它们合成为更为复杂的表达式。它不可以被用于创建第一级的表达式(也就是由解析器解析出的语法成分,合成出表达式。这些步骤需要远为复杂的函数来处理,在后面我们可以看到一些代码片断)。尽管如此,build还是被广泛使用,因为在C/C++程序中,表达式的表现力在于它的嵌套能力及它能出现的地方。把简单表达式节点,复合成复杂表达式节点,正是build的任务。

1.3.1. 节点的创建

我们已经知道,源代码将前端转换为语义相同的树形式。而树的节点有多种形式以对应不同的语义成分。这里函数build将表达式创建tree_exp类型的节点。

1.3.1.1.    tree_exp类型

在前端,表达式由tree_exp类型节点来表示。它的定义如下:

 

852  struct tree_exp GTY(())                                                                                     in tree.h

853  {

854    struct tree_common common;

855    int complexity;

856    tree GTY ((special ("tree_exp"),

857              desc ("TREE_CODE ((tree) &%0)")))

858      operands[1];

859  };

 

作为树节点的一种,tree_exp也是由make_node来分配所需的内存。

 

2295 tree

2296 build (enum tree_code code, tree tt, ...)                                                              in tree.c

2297 {

2298   tree t;

2299   int length;

2300   int i;

2301   int fro;

2302   int constant;

2303   va_list p;

2304   tree node;

2305

2306   va_start (p, tt);

2307

2308   t = make_node (code);

2309   length = TREE_CODE_LENGTH (code);

2310   TREE_TYPE (t) = tt;

 

注意2306行的va_start,它不是我们程序中的那个va_start。在GCC中,它是所谓的内建函数(builtin function)。在后面我们会碰到内建函数。

1.3.1.2.      操作数的预处理

函数build接受的参数的数量是可变的,因此函数首先要确定参数的数目。这个数目取决于表达式的类型(通过树码)。

 

build (continued)

 

2312   /* Below, we automatically set TREE_SIDE_EFFECTS and TREE_READONLY for the

2313     result based on those same flags for the arguments. But if the

2314     arguments aren't really even `tree' expressions, we shouldn't be trying

2315     to do this.  */

2316   fro = first_rtl_op (code);

2317

2318   /* Expressions without side effects may be constant if their

2319     arguments are as well.  */

2320   constant = (TREE_CODE_CLASS (code) == '<'

2321                || TREE_CODE_CLASS (code) == '1'

2322                || TREE_CODE_CLASS (code) == '2'

2323                || TREE_CODE_CLASS (code) == 'c');

 

在上面2316行,first_rtl_op可以根据要创建的表达式找出对应的参数的数目。

 

1448 int

1449 first_rtl_op (enum tree_code code)                                                                    in tree.c

1450 {

1451   switch (code)

1452   {

1453     case SAVE_EXPR:

1454       return 2;

1455     case GOTO_SUBROUTINE_EXPR:

1456     case RTL_EXPR:

1457       return 0;

1458     case WITH_CLEANUP_EXPR:

1459       return 2;

1460     default:

1461       return TREE_CODE_LENGTH (code);

1462   }

1463 }

 

2330行,比较表达式(类别“<”),一元算术表达式(类别“1”),二元算术表达式(类别“2”),常量表达式(类别“c”)是没有副作用(side effect)的表达式,只要它们的实参没有副作用,而且它们是常量性的如果实参是常量性的。

 

build (continued)

 

2325   if (length == 2)

2326   {

2327     /* This is equivalent to the loop below, but faster.  */

2328     tree arg0 = va_arg (p, tree);

2329     tree arg1 = va_arg (p, tree);

2330

2331     TREE_OPERAND (t, 0) = arg0;

2332     TREE_OPERAND (t, 1) = arg1;

2333     TREE_READONLY (t) = 1;

2334     if (arg0 && fro > 0)

2335     {

2336       if (TREE_SIDE_EFFECTS (arg0))

2337         TREE_SIDE_EFFECTS (t) = 1;

2338       if (!TREE_READONLY (arg0))

2339         TREE_READONLY (t) = 0;

2340       if (!TREE_CONSTANT (arg0))

2341         constant = 0;

2342     }

2343

2344     if (arg1 && fro > 1)

2345     {

2346       if (TREE_SIDE_EFFECTS (arg1))

2347         TREE_SIDE_EFFECTS (t) = 1;

2348       if (!TREE_READONLY (arg1))

2349         TREE_READONLY (t) = 0;

2350       if (!TREE_CONSTANT (arg1))

2351         constant = 0;

2352     }

2353   }

2354   else if (length == 1)

2355   {

2356     tree arg0 = va_arg (p, tree);

2357

2358     /* The only one-operand cases we handle here are those with side-effects.

2359       Others are handled with build1. So don't bother checked if the

2360       arg has side-effects since we'll already have set it.

2361

2362       ??? This really should use build1 too.  */

2363     if (TREE_CODE_CLASS (code) != 's')

2364       abort ();

2365     TREE_OPERAND (t, 0) = arg0;

2366   }

2367   else

2368   {

2369     for (i = 0; i < length; i++)

2370     {

2371       tree operand = va_arg (p, tree);

2372

2373       TREE_OPERAND (t, i) = operand;

2374       if (operand && fro > i)

2375       {

2376         if (TREE_SIDE_EFFECTS (operand))

2377           TREE_SIDE_EFFECTS (t) = 1;

2378         if (!TREE_CONSTANT (operand))

2379           constant = 0;

2380       }

2381     }

2382   }

2383   va_end (p);

1.3.2. 对于CALL_EXPR节点的处理

对于除CALL_EXPR外的表达式,其节点就已经创建好了。但对于CALL_EXPR,其副作用取决于其调用的函数,而不在CALL_EXPR节点本身(对于刚创建的CALL_EXPR节点,TREE_SIDE_EFFECTS一定为0)。因此,需要额外代码走入调用函数来确定其副作用。

 

build (continued)

 

2385   TREE_CONSTANT (t) = constant;

2386   

2387   if (code == CALL_EXPR && !TREE_SIDE_EFFECTS (t))

2388   {

2389     /* Calls have side-effects, except those to const or

2390       pure functions.  */

2391     i = call_expr_flags (t);

2392     if (!(i & (ECF_CONST | ECF_PURE)))

2393       TREE_SIDE_EFFECTS (t) = 1;

2394

2395     /* And even those have side-effects if their arguments do.  */

2396     else for (node = TREE_OPERAND (t, 1); node; node = TREE_CHAIN (node))

2397     if (TREE_SIDE_EFFECTS (TREE_VALUE (node)))

2398     {

2399       TREE_SIDE_EFFECTS (t) = 1;

2400       break;

2401     }

2402   }

2403

2404   return t;

2405 }

1.3.2.1.      确定被调用的函数
1.3.2.1.1.            CALL_EXPR节点

首先看一下CALL_EXPR节点。

CALL_EXPR[2]

²        这个节点用于表示对函数包括非静态成员方法的调用。其第一个参数是指向被调用函数的指针,它应该是类型为POINTER_TYPE 的表达式。第二个参数是一个TREE_LIST,函数调用的参数按从左到右的次序出现在这个链表中。其中节点的TREE_VALUE包含实参的表达式(节点1TREE_PURPOSE值不被使用,应被忽略)。对于非静态的成员方法,会有一个对应于this指针的参数。所有的参数在链表中都应该有对应的表达式,就算函数被声明有缺省参数,而一些实参在调用处没有显式给出。

 

751  int

752  call_expr_flags (tree t)                                                                               in calls.c

753  {

754    int flags;

755    tree decl = get_callee_fndecl (t);

756 

757    if (decl)

758      flags = flags_from_decl_or_type (decl);

759    else

760    {

761      t = TREE_TYPE (TREE_OPERAND (t, 0));

762      if (t && TREE_CODE (t) == POINTER_TYPE)

763        flags = flags_from_decl_or_type (TREE_TYPE (t));

764      else

765        flags = 0;

766    }

767 

768    return flags;

769  }

 

在上面755get_callee_fndecl被用于确定被调用函数的地址。正如上述CALL_EXPR所揭示的它的第一个参数是指向被调用函数的指针它是类型为POINTER_TYPE 的表达式。我们首先要看一下何为FUNCTION_DECL节点,它是这个指针所指向的对象。

1.3.2.1.2.            找出调用的函数

FUNCTION_DECL节点是什么呢?

FUNCTION_DECL[2]

²        函数在前端由FUNCTION_DECL节点来表示。而一组重载函数有时由一个OVERLOAD节点来代表。

一个OVERLOAD节点不代表一个声明,因此不能将用于DECL_*的宏用于OVERLOAD节点。一个OVERLOAD节点类似于一个TREE_LIST。用宏OVL_CURRENT可以获取与之关联的函数;而用OVL_NEXT则得到重载函数链表中的下一个OVERLOAD节点。 OVL_CURRENTOVL_NEXT实际上是多态的(polymorphic);你也可以把它们用于FUNCTION_DECL节点。在用于FUNCTION_DECL节点时,OVL_CURRENT将永远返回函数本身,而OVL_NEXT则永远返回NULL_TREE

为了确定函数的绑定域(the scope of a function),你可以使用宏DECL_CONTEXT。这个宏会返回类(以RECORD_TYPE节点或者UNION_TYPE节点)或者名字空间(以NAMESPACE_DECL节点),而函数则是其中的成员。对于虚函数,这个宏返回函数被真正定义的类,而不是只是声明它的基类。

如果一个友元函数被定义于一个类的作用域,宏DECL_FRIEND_CONTEXT可以被用于确定定义该函数的类。例如,在代码:“class C { friend void f() {} };”中,对应于fDECL_CONTEXT会是全局名字空间global_namespace。但是相应的,fDECL_FRIEND_CONTEXT将是类CRECORD_TYPE节点。

C语言中,一个函数的DECL_CONTEXT可能是另一个函数。这个形式表明GNU的嵌套函数的扩展正被使用。至于嵌套函数详细的语言,参考GCC的手册[6]。被嵌套的函数可以访问包含函数的局部变量。这种访问不会在树结构中显式给出,后端必须查看被引用的VAR_DECLDECL_CONTEXT。如果被引用的VAR_DECLDECL_CONTEXT不是当前被处理的函数,而且VAR_DECL不持有DECL_EXTERNALDECL_STATIC,那么该引用即是对包含函数的局部变量,后端将采取相应的行为。

在下面4482行,STRIP_NOPS剥除最上层不改变机器模式的NON_LVALUE_EXPRNOP_EXPR节点(更准确的,是跳过这些节点,逐级进入其操作数)。对于普通的函数调用,通过STRIP_NOP后,可能就会碰到FUNCTION_DECL节点。

4485行的DECL_P,如果是非0值,表明addr是声明。而如果addr是声明,但不是 FUNCTION_DECL,它就被假定为函数指针,而且如果它是只读和非易变的(read-only and non-volatile),那么它的初始值包含了我们需要的地址。

4492行,我们期望得到包含FUNCTION_DECLADDR_EXPR。其所包含的FUNCTION_DECL正是我们所寻求的。

 

4468 tree

4469 get_callee_fndecl (tree call)                                                                                     in tree.c

4470 {

4471   tree addr;

4472

4473   /* It's invalid to call this function with anything but a

4474     CALL_EXPR.  */

4475   if (TREE_CODE (call) != CALL_EXPR)

4476     abort ();

4477

4478   /* The first operand to the CALL is the address of the function

4479     called.  */

4480   addr = TREE_OPERAND (call, 0);

4481

4482   STRIP_NOPS (addr);

4483

4484   /* If this is a readonly function pointer, extract its initial value.  */

4485   if (DECL_P (addr) && TREE_CODE (addr) != FUNCTION_DECL

4486       && TREE_READONLY (addr) && ! TREE_THIS_VOLATILE (addr)

4487       && DECL_INITIAL (addr))

4488     addr = DECL_INITIAL (addr);

4489

4490   /* If the address is just `&f' for some function `f', then we know

4491     that `f' is being called.  */

4492   if (TREE_CODE (addr) == ADDR_EXPR

4493       && TREE_CODE (TREE_OPERAND (addr, 0)) == FUNCTION_DECL)

4494     return TREE_OPERAND (addr, 0);

4495   

4496   /* We couldn't figure out what was being called. Maybe the front

4497     end has some idea.  */

4498   return (*lang_hooks.lang_get_callee_fndecl) (call);

4499 }

 

如果我们仍然不能确定被调用的函数,则在4498行,lang_hooks 中的钩子lang_get_callee_fndecl 被调用。默认的,函数lhd_return_null_tree将被调用,但它不做任何事。前端可以为这个钩子绑定合适的函数。对于C/C++,默认的函数将被使用。

1.3.2.1.3.            确定函数的属性
1.3.2.1.3.1.  获取描述被调用函数的数据

函数flags_from_decl_or_type确定函数的属性。在编译期间GCC会创建一个图(graph)来描述被调用函数和调用者。

 

698  int

699  flags_from_decl_or_type (tree exp)                                                             in calls.c

700  {

701    int flags = 0;

702    tree type = exp;

703 

704    if (DECL_P (exp))

705    {

706      struct cgraph_rtl_info *i = cgraph_rtl_info (exp);

707      type = TREE_TYPE (exp);

1.3.2.1.3.2.  函数调用关系图

1.3.2.1.3.2.1.              节点结构

上面706行,cgraph_rtl_info通过cgraph_node获取该函数对应的关系图中的节点。如果decl为当前处理的函数(current_function_decl指向当前被处理的函数),直接返回相应的cgraph_rtl_info节点。否则,该函数必须已经被汇编。

 

348  struct cgraph_rtl_info *

349  cgraph_rtl_info (tree decl)                                                                           in cgraph.c

350  {

351    struct cgraph_node *node;

352    if (TREE_CODE (decl) != FUNCTION_DECL)

353      abort ();

354    node = cgraph_node (decl);

355    if (decl != current_function_decl

356        && !TREE_ASM_WRITTEN (node->decl))

357      return NULL;

358    return &node->rtl;

359  }

 

下面的结构体cgraph_node作为图中的一个节点,用于描述调用关系中的一个函数。GCC支持嵌套函数,因此下面域originnestednext_nested用于描述该函数在嵌套中的层次和关系。

 

85    struct cgraph_node GTY((chain_next ("%h.next"), chain_prev ("%h.previous")))    in cgraph.h

86    {

87      tree decl;

88      struct cgraph_edge *callees;

89      struct cgraph_edge *callers;

90      struct cgraph_node *next;

91      struct cgraph_node *previous;

92      /* For nested functions points to function the node is nested in.  */

93      struct cgraph_node *origin;

94      /* Points to first nested function, if any.  */

95      struct cgraph_node *nested;

96      /* Pointer to the next function with same origin, if any.  */

97      struct cgraph_node *next_nested;

98      /* Pointer to the next function in cgraph_nodes_queue.  */

99      struct cgraph_node *next_needed;

100    PTR GTY ((skip (""))) aux;

101 

102    struct cgraph_local_info local;

103    struct cgraph_global_info global;

104    struct cgraph_rtl_info rtl;

105    /* Unique id of the node.  */

106    int uid;

107    /* Set when function must be output - it is externally visible

108      or it's address is taken.  */

109    bool needed;

110    /* Set when function is reachable by call from other function

111      that is either reachable or needed.  */

112    bool reachable;

113    /* Set once the function has been instantiated and its callee

114      lists created.  */

115    bool analyzed;

116    /* Set when function is scheduled to be assembled.  */

117    bool output;

118  };

 

在上面的结构体中,cgraph_edge用作连接节点的边,它保存了一个函数作为调用者和被调用者的全部信息。

 

120  struct cgraph_edge GTY(())                                                                         in cgraph.h

121  {

122    struct cgraph_node *caller;

123    struct cgraph_node *callee;

124    struct cgraph_edge *next_caller;

125    struct cgraph_edge *next_callee;

126    /* When NULL, inline this call. When non-NULL, points to the explanation

127      why function was not inlined.  */

128    const char *inline_failed;

129  };

 

在关系图中,调用者和被调用者的关系由cgraph_nodecgraph_edge来共同表示。如下图所示,函数N1调用N3N3调用N4N4返回,N2接着调用N3N3调用N5。图中,N1N5按创建的次序,通过next域连起来(对previous亦然)。在N3cgraph_node节点中,域caller指向Edge1-3(第1个调用N3的函数)。Edge1-3caller指向N1callee指向N3,同时next_caller指向Edge2-3(第2个调用N3的函数)。另外,在N3cgraph_node节点中,域callee指向Edge3-4N31个调用的函数),而Edge3-4next_callee指向Edge3-5N32个调用的函数),如果N3继续调用别的函数,Edge3-5next_callee继续指向对应的cgraph_edge。而对于N5Edge3-5则是caller,它的next_caller域指向下一个调用N5cgraph_edge

gcc-1

1 函数调用关系图示例

 

cgraph_node定义的102行,结构体cgraph_local_info包含了在本地收集的函数信息。它在函数被分析后可用。

 

28    struct cgraph_local_info GTY(())                                                                 in cgraph.h

29    {

30      /* Size of the function before inlining.  */

31      int self_insns;

32   

33      /* Set when function function is visible in current compilation unit only

34        and it's address is never taken.  */

35      bool local;

36      /* Set once it has been finalized so we consider it to be output.  */

37      bool finalized;

38   

39      /* False when there something makes inlining impossible (such as va_arg).  */

40      bool inlinable;

41      /* True when function should be inlined independently on it's size.  */

42      bool disregard_inline_limits;

43     /* True when the function has been originally extern inline, but it is

44        redefined now.  */

45      bool redefined_extern_inline;

46    };

 

而在103行,结构体cgraph_global_info包含了那些当编译结束时,需要全局计算的函数信息。它仅在使用选项-funit-at-time时可用。

 

51    struct cgraph_global_info GTY(())                                                                      in cgraph.h

52    {

53      /* Estimated size of the function after inlining.  */

54      int insns;

55   

56      /* Number of times given function will be cloned during output.  */

57      int cloned_times;

58   

59      /* Set when the function will be inlined exactly once.  */

60      bool inline_once;

61   

62     /* Set to true for all reachable functions before inlining is decided.

63        Once we inline all calls to the function and the function is local,

64        it is set to false.  */

65      bool will_be_output;

66   

67      /* Set iff at least one of the caller edges has inline_call flag set.  */

68      bool inlined;

69    };

 

104行,结构体cgraph_rtl_info则包含由后端传播的函数信息。当函数的汇编完成后,该信息才可用。

 

74    struct cgraph_rtl_info GTY(())                                                                    in cgraph.h

75    {

76       bool const_function;

77       bool pure_function;

78       int preferred_incoming_stack_boundary;

79    };

1.3.1.1.1.1.1.              创建cgaph_node节点

函数cgraph_node创建cgraph_node的对象。正如我们前面所见,所有的cgraph_node节点都连成双向链表。

 

95    struct cgraph_node *

96    cgraph_node (tree decl)                                                                                     in cgraph.c

97    {

98      struct cgraph_node *node;

99      struct cgraph_node **slot;

100 

101    if (TREE_CODE (decl) != FUNCTION_DECL)

102      abort ();

103 

104    if (!cgraph_hash)

105      cgraph_hash = htab_create_ggc (10, hash_node, eq_node, NULL);

106 

107    slot = (struct cgraph_node **)

108      htab_find_slot_with_hash (cgraph_hash, DECL_ASSEMBLER_NAME (decl),

109                             IDENTIFIER_HASH_VALUE

110                               (DECL_ASSEMBLER_NAME (decl)), INSERT);

111    if (*slot)

112      return *slot;

113    node = ggc_alloc_cleared (sizeof (*node));

114    node->decl = decl;

115    node->next = cgraph_nodes;

116    node->uid = cgraph_max_uid++;

117    if (cgraph_nodes)

118      cgraph_nodes->previous = node;

119    node->previous = NULL;

120    cgraph_nodes = node;

121    cgraph_n_nodes++;

122    *slot = node;

123    if (DECL_CONTEXT (decl) && TREE_CODE (DECL_CONTEXT (decl)) == FUNCTION_DECL)

124    {

125      node->origin = cgraph_node (DECL_CONTEXT (decl));

126      node->next_nested = node->origin->nested;

127      node->origin->nested = node;

128    }

129    return node;

130  }

 

同时所有的cgraph_node节点都被存放到哈希表里,以保证与函数一一对应。

1.3.1.1.1.2.  确定属性

上面提到cgraph_rtl_info需要函数被汇编后才可用(否则函数cgraph_rtl_info返回NULL)。但如果可用,这个数据是最准确的(汇编已经就绪了)。

 

flags_from_decl_or_type (continued)

 

698      if (i)

699      {

700        if (i->pure_function)

701          flags |= ECF_PURE | ECF_LIBCALL_BLOCK;

702        if (i->const_function)

703          flags |= ECF_CONST | ECF_LIBCALL_BLOCK;

704      }

705 

706      /* The function exp may have the `malloc' attribute.  */

707      if (DECL_IS_MALLOC (exp))

708        flags |= ECF_MALLOC;

709 

710      /* The function exp may have the `pure' attribute.  */

711      if (DECL_IS_PURE (exp))

712        flags |= ECF_PURE | ECF_LIBCALL_BLOCK;

713 

714      if (TREE_NOTHROW (exp))

715        flags |= ECF_NOTHROW;

716 

717      if (TREE_READONLY (exp) && ! TREE_THIS_VOLATILE (exp))

718        flags |= ECF_LIBCALL_BLOCK;

719    }

720 

721    if (TREE_READONLY (exp) && ! TREE_THIS_VOLATILE (exp))

722      flags |= ECF_CONST;

723 

724    if (TREE_THIS_VOLATILE (exp))

725      flags |= ECF_NORETURN;

726 

727    /* Mark if the function returns with the stack pointer depressed. We

728      cannot consider it pure or constant in that case.  */

729    if (TREE_CODE (type) == FUNCTION_TYPE && TYPE_RETURNS_STACK_DEPRESSED (type))

730    {

731      flags |= ECF_SP_DEPRESSED;

732      flags &= ~(ECF_PURE | ECF_CONST | ECF_LIBCALL_BLOCK);

733    }

734 

735    return flags;

736  }

 

而上面所用到的标示位的含义如下:

ECF_CONST,如果非零,表示对一个常量函数的调用。

ECF_NORETURN,如果非零,表示对一个volatile函数的调用。

ECF_MALLOC,如果非零,表示一个对malloc或相关函数的调用。

ECF_MAY_BE_ALLOCA,如果非零,貌似表示一个对alloca的调用。

ECF_NOTHROW,如果非零,表示对一个不会抛出异常的函数的调用。

ECF_RETURNS_TWICE,如果非零,表示一个对setjmp或相关函数的调用。

ECF_LONGJMP,如果非零,表示一个对longjmp的调用。

ECF_FORK_OR_EXEC, ECF_SIBCALL,如果非零,这是一个使用当前影像(image)创建新进程的系统调用(syscall)。

ECF_PURE,如果非零,是一个对纯函数(pure function)的调用。在[6]中,对纯函数有如下描述:

许多函数除了返回值外,没有别的影响。而且它们的返回值依赖于函数参数和(或者)全局变量。这样的函数和算术操作符类似,适用于循环优化和共用子表达式消除。这些函数在声明中应该使用属性pure。常见的纯函数例子有strlenmemcmp

ECF_SP_DEPRESSED,如果非零,是一个返回时不调整栈指针的函数(用于Ada,使得被调用函数可以返回一个调用者不知道大小的对象)。

ECF_ALWAYS_RETURN,如果非零,这个调用已知永远返回。

ECF_LIBCALL_BLOCK,在调用外创建libcall块(emit_libcall_block)。

回到build的最后部分,相应的设置创建的tree_exp对象的side_effects_flag。注意,只要函数是纯的或常量属性的,就认为它有副作用(也即不可随便更改它调用的次数)。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值