函数build可被用于为表达式创建节点。但它只接受表达式作为参数,把它们合成为更为复杂的表达式。它不可以被用于创建第一级的表达式(也就是由解析器解析出的语法成分,合成出表达式。这些步骤需要远为复杂的函数来处理,在后面我们可以看到一些代码片断)。尽管如此,build还是被广泛使用,因为在C/C++程序中,表达式的表现力在于它的嵌套能力及它能出现的地方。把简单表达式节点,复合成复杂表达式节点,正是build的任务。
我们已经知道,源代码将前端转换为语义相同的树形式。而树的节点有多种形式以对应不同的语义成分。这里函数build将表达式创建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)。在后面我们会碰到内建函数。
函数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);
对于除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 }
首先看一下CALL_EXPR节点。
CALL_EXPR[2]
² 这个节点用于表示对函数包括非静态成员方法的调用。其第一个参数是指向被调用函数的指针,它应该是类型为POINTER_TYPE 的表达式。第二个参数是一个TREE_LIST,函数调用的参数按从左到右的次序出现在这个链表中。其中节点的TREE_VALUE包含实参的表达式(节点1的TREE_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 }
在上面755行,get_callee_fndecl被用于确定被调用函数的地址。正如上述CALL_EXPR所揭示的,它的第一个参数是指向被调用函数的指针,它是类型为POINTER_TYPE 的表达式。我们首先要看一下何为FUNCTION_DECL节点,它是这个指针所指向的对象。
FUNCTION_DECL节点是什么呢?
FUNCTION_DECL[2]
² 函数在前端由FUNCTION_DECL节点来表示。而一组重载函数有时由一个OVERLOAD节点来代表。
一个OVERLOAD节点不代表一个声明,因此不能将用于DECL_*的宏用于OVERLOAD节点。一个OVERLOAD节点类似于一个TREE_LIST。用宏OVL_CURRENT可以获取与之关联的函数;而用OVL_NEXT则得到重载函数链表中的下一个OVERLOAD节点。 宏OVL_CURRENT和OVL_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() {} };”中,对应于f的DECL_CONTEXT会是全局名字空间global_namespace。但是相应的,f的DECL_FRIEND_CONTEXT将是类C的RECORD_TYPE节点。
在C语言中,一个函数的DECL_CONTEXT可能是另一个函数。这个形式表明GNU的嵌套函数的扩展正被使用。至于嵌套函数详细的语言,参考GCC的手册[6]。被嵌套的函数可以访问包含函数的局部变量。这种访问不会在树结构中显式给出,后端必须查看被引用的VAR_DECL的DECL_CONTEXT。如果被引用的VAR_DECL的DECL_CONTEXT不是当前被处理的函数,而且VAR_DECL不持有DECL_EXTERNAL及DECL_STATIC,那么该引用即是对包含函数的局部变量,后端将采取相应的行为。
在下面4482行,STRIP_NOPS剥除最上层不改变机器模式的NON_LVALUE_EXPR和NOP_EXPR节点(更准确的,是跳过这些节点,逐级进入其操作数)。对于普通的函数调用,通过STRIP_NOP后,可能就会碰到FUNCTION_DECL节点。
在4485行的DECL_P,如果是非0值,表明addr是声明。而如果addr是声明,但不是 FUNCTION_DECL,它就被假定为函数指针,而且如果它是只读和非易变的(read-only and non-volatile),那么它的初始值包含了我们需要的地址。
在4492行,我们期望得到包含FUNCTION_DECL的ADDR_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++,默认的函数将被使用。
函数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. 节点结构
上面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支持嵌套函数,因此下面域origin,nested,next_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_node和cgraph_edge来共同表示。如下图所示,函数N1调用N3,N3调用N4,N4返回,N2接着调用N3,N3调用N5。图中,N1至N5按创建的次序,通过next域连起来(对previous亦然)。在N3的cgraph_node节点中,域caller指向Edge1-3(第1个调用N3的函数)。Edge1-3的caller指向N1,callee指向N3,同时next_caller指向Edge2-3(第2个调用N3的函数)。另外,在N3的cgraph_node节点中,域callee指向Edge3-4(N3第1个调用的函数),而Edge3-4的next_callee指向Edge3-5(N3第2个调用的函数),如果N3继续调用别的函数,Edge3-5的next_callee继续指向对应的cgraph_edge。而对于N5,Edge3-5则是caller,它的next_caller域指向下一个调用N5的cgraph_edge。
图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节点都被存放到哈希表里,以保证与函数一一对应。
上面提到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。常见的纯函数例子有strlen和memcmp。
ECF_SP_DEPRESSED,如果非零,是一个返回时不调整栈指针的函数(用于Ada,使得被调用函数可以返回一个调用者不知道大小的对象)。
ECF_ALWAYS_RETURN,如果非零,这个调用已知永远返回。
ECF_LIBCALL_BLOCK,在调用外创建libcall块(emit_libcall_block)。
回到build的最后部分,相应的设置创建的tree_exp对象的side_effects_flag。注意,只要函数是纯的或常量属性的,就认为它有副作用(也即不可随便更改它调用的次数)。