回到 finish_file ,我们已经看到 flag_unit_at_a_time 在‘ -O2 ’以上优化级别中设置为 1 。这个标记也可以由选项 -funit-at-a-time 来设定,【 6 】对其描述如下:
在开始产生代码之前,解析整个编译单元。这允许进行某些额外的优化,不过会消耗更多内存(总体而言)。使用 unit-at-a-time 模式有一些兼容性问题: 使能 unit-at-a-time 模式可能改变函数,变量及最顶层汇编语句的发布次序,并将很可能破坏依赖于特定次序的代码。然而大多数这样的最顶层汇编语句可以为段属性所替代。选项‘ fno-toplevel-reorder ’可以被用来保持输入文件中的次序,以某些优化作为代价。 unit-at-a-time 模式移除未被引用的静态变量及函数。当一个汇编语句直接指向除此之外不被使用的变量或函数时,这可能导致未定义的引用。在这个情形下,要么把该变量、函数列为这个汇编语句的一个操作数,或者在顶层汇编语句的情形下,属性 used 应该用在声明中。 静态函数现在可以使用破坏直接调用函数的汇编语句的非标准传入约定。再一次,属性 used 将防止这个行为。 作为一个临时的变通,可以使用‘ -fno-unit-at-a-time ’,不过这个方案可能不会被今后的 GCC 支持。 |
finish_file (continue)
2850 if (flag_unit_at_a_time )
2851 {
2852 cgraph_finalize_compilation_unit ();
2853 cgraph_optimize ();
2854 }
为执行优化,我们需要知道代码间的关系,然后我们可以根据探测出的关系剥除重复的部分。因此在这里,下面的函数分析编译单元并构建一个图来描述其中的依赖关系,数据流及控制流。
368 void
369 cgraph_finalize_compilation_unit (void) in cgraphunit.c
370 {
371 struct cgraph_node *node;
372
373 if (!flag_unit_at_a_time )
374 {
375 cgraph_assemble_pending_functions ();
376 return ;
377 }
378
379 cgraph_varpool_assemble_pending_decls ();
380 if (!quiet_flag )
381 fprintf (stderr , "/nAnalyzing compilation unit/n");
记得如果声明在这个编译单元以外可见,它被记录入队列 cgraph_varpool_nodes_queue 。在这里如果这个队列不是空的,所保存的声明应该已经有汇编产生;否则就应该在这里产生。
612 bool
613 cgraph_varpool_assemble_pending_decls (void) in cgraph.c
614 {
615 bool changed = false;
616
617 while (cgraph_varpool_nodes_queue )
618 {
619 tree decl = cgraph_varpool_nodes_queue ->decl;
620 struct cgraph_varpool_node *node = cgraph_varpool_nodes_queue ;
621
622 cgraph_varpool_nodes_queue = cgraph_varpool_nodes_queue ->next_needed;
623 if (!TREE_ASM_WRITTEN (decl))
624 {
625 assemble_variable (decl, 0, 1, 0);
626 changed = true;
627 }
628 node->next_needed = NULL;
629 }
630 return changed;
631 }
上面的 623 行,在一个 VAR_DECL 中, TREE_ASM_WRITTEN 如果不是 0 ,表示已经写入汇编代码;否则就要调用 assemble_variable 。注意自动变量不使用这个函数发布汇编。
在这次调用中,参数 don’t_output_data 是 0 ,表示确实输出初始值。那么在下面 1344 行,在当前这个版本中,对于所有的前端,钩子 prepare_assemble_variable 都是 NULL 。
1335 void
1336 assemble_variable (tree decl, int top_level ATTRIBUTE_UNUSED, in varasm.c
1337 int at_end ATTRIBUTE_UNUSED, int dont_output_data)
1338 {
1339 const char *name;
1340 unsigned int align;
1341 int reloc = 0;
1342 rtx decl_rtl;
1343
1344 if (lang_hooks .decls.prepare_assemble_variable)
1345 (*lang_hooks .decls.prepare_assemble_variable) (decl);
1346
1347 last_assemble_variable_decl = 0;
1348
1349 /* Normally no need to say anything here for external references,
1350 since assemble_external is called by the language-specific code
1351 when a declaration is first seen. */
1352
1353 if (DECL_EXTERNAL (decl))
1354 return ;
1355
1356 /* Output no assembler code for a function declaration.
1357 Only definitions of functions output anything. */
1358
1359 if (TREE_CODE (decl) == FUNCTION_DECL)
1360 return ;
1361
1362 /* Do nothing for global register variables. */
1363 if (DECL_RTL_SET_P (decl) && GET_CODE (DECL_RTL (decl)) == REG)
1364 {
1365 TREE_ASM_WRITTEN (decl) = 1;
1366 return ;
1367 }
1368
1369 /* If type was incomplete when the variable was declared,
1370 see if it is complete now. */
1371
1372 if (DECL_SIZE (decl) == 0)
1373 layout_decl (decl, 0);
1374
1375 /* Still incomplete => don't allocate it; treat the tentative defn
1376 (which is what it must have been) as an `extern' reference. */
1377
1378 if (!dont_output_data && DECL_SIZE (decl) == 0)
1379 {
1380 error ("%Jstorage size of `%D' isn't known", decl, decl);
1381 TREE_ASM_WRITTEN (decl) = 1;
1382 return ;
1383 }
1384
1385 /* The first declaration of a variable that comes through this function
1386 decides whether it is global (in C, has external linkage)
1387 or local (in C, has internal linkage). So do nothing more
1388 if this function has already run. */
1389
1390 if (TREE_ASM_WRITTEN (decl))
1391 return ;
1392
1393 /* Make sure targetm.encode_section_info is invoked before we set
1394 ASM_WRITTEN. */
1395 decl_rtl = DECL_RTL (decl);
1396
1397 TREE_ASM_WRITTEN (decl) = 1;
1398
1399 /* Do no output if -fsyntax-only. */
1400 if (flag_syntax_only )
1401 return ;
能从其初始值构建关联的 RTL 节点的节点必须是已完成的。在前端中,查看中间树节点是否已完成,是检查其尺寸。因为每个由 make_node 创建的节点总是具有尺寸 0 ;而仅在 layout_decl 中,才会计算并设置这个大小尺寸。
然后在 1395 行,宏 DECL_RTL 为这个 VAR_DECL 产生了下面的 rtx 节点,而变量 decl_rtl 则指向下面的 rtx 的 MEM 节点(对于这个例子, VAR_DECL 必须不能被声明为 register )。
那么在下面的代码片段中,开始配置为这个 VAR_DECL 在 MEM 节点中分配的内存。
assemble_variable (continue)
1403 app_disable ();
1404
1405 if (! dont_output_data
1406 && ! host_integerp (DECL_SIZE_UNIT (decl), 1))
1407 {
1408 error ("%Jsize of variable '%D' is too large", decl, decl);
1409 return ;
1410 }
1411
1412 name = XSTR (XEXP (decl_rtl, 0), 0);
1413 if (TREE_PUBLIC (decl) && DECL_NAME (decl))
1414 notice_global_symbol (decl);
1415
1416 /* Compute the alignment of this data. */
1417
1418 align = DECL_ALIGN (decl);
1419
1420 /* In the case for initialing an array whose length isn't specified,
1421 where we have not yet been able to do the layout,
1422 figure out the proper alignment now. */
1423 if (dont_output_data && DECL_SIZE (decl) == 0
1424 && TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE)
1425 align = MAX (align, TYPE_ALIGN (TREE_TYPE (TREE_TYPE (decl))));
1426
1427 /* Some object file formats have a maximum alignment which they support.
1428 I n particular, a.out format supports a maximum alignment of 4. */
1429 #ifndef MAX_OFILE_ALIGNMENT
1430 #define MAX_OFILE_ALIGNMENT BIGGEST_ALIGNMENT
1431 #endif
1432 if (align > MAX_OFILE_ALIGNMENT)
1433 {
1434 warning ("%Jalignment of '%D' is greater than maximum object "
1435 "file alignment. Using %d", decl, decl,
1436 MAX_OFILE_ALIGNMENT/BITS_PER_UNIT);
1437 align = MAX_OFILE_ALIGNMENT;
1438 }
1439
1440 /* On some machines, it is good to increase alignment sometimes. */
1441 if (! DECL_USER_ALIGN (decl))
1442 {
1443 #ifdef DATA_ALIGNMENT
1444 align = DATA_ALIGNMENT (TREE_TYPE (decl), align);
1445 #endif
1446 #ifdef CONSTANT_ALIGNMENT
1447 if (DECL_INITIAL (decl) != 0 && DECL_INITIAL (decl) != error_mark_node)
1448 align = CONSTANT_ALIGNMENT (DECL_INITIAL (decl), align);
1449 #endif
1450 }
1451
1452 /* Reset the alignment in case we have made it tighter, so we can benefit
1453 from it in get_pointer_alignment. */
1454 DECL_ALIGN (decl) = align;
1455 set_mem_align (decl_rtl, align);
1456
1457 if (TREE_PUBLIC (decl))
1458 maybe_assemble_visibility (decl);
首先,作为 GNU 汇编器的语法,如果一个输入文件的第一行是 #NO_APP ,或者如果使用了选项‘ -f ’,空格及注释不会从输入文件中移走。在一个输入文件中,你可以要求在特定的部分移走空格及注释,通过在这个可能包含空格及注释的文本前加入一行 #APP ,并在该文本后加上一行 #NO_APP 。这个特性主要目的在于支持编译器中的汇编语句,编译器的输出是没有空格及注释的。在 C++ 中,这主要用于嵌入汇编器,并在函数作用域外的嵌入汇编器块的开头, GCC 将自动插入 #APP 。因此在 1403 行,如果使用了 #APP ,在发布汇编之前,首先通过 app_disable 使用 #NO_APP 关闭这个 #APP 。
284 void
285 app_disable (void) in final.c
286 {
287 if (app_on )
288 {
289 fputs (ASM_APP_OFF, asm_out_file );
290 app_on = 0;
291 }
292 }
全局变量 app_on 如果非 0 ,表示 #APP 在使用;而 ASM_APP_OFF ,对于绝大多数目标平台被定义为“ #NO_APP ”(在剩下的少数中,它被定义为空串)。
接着在 1414 行, notice_global_symbol 收集在这个编译单元中出现的第一个全局变量的名字,它将被用于为匿名名字空间产生内部名字。上面代码的最后部分则是确定该内存的对齐量,即便对于 Linux/x86 目标平台,在 1429 行, MAX_OFILE_ALIGNMENT 仍未定义,而被设置为下面的 BIGGEST_ALIGNMENT (定义为 32 )。
我们已经看到的,除非显式地使用属性在声明中指出特别的对齐量,节点中的总是 false 。而在通常的情形中,目标平台会定义 DATA_ALIGNMENT 来增加中等大小数据的对齐量,以使它适配更少的缓存线( cache line )。另外 CONSTANT_ALIGNMENT 也被目标平台定义来增加字符串常量的对齐量到字边界,使得调用‘ strcpy ’拷贝常量可以内联地执行。然后这个精心确定的对齐量将被设置入 rtx 的 MEM 节点中,它将在后面指引汇编的发布。
1822 void
1823 set_mem_align (rtx mem, unsigned int align) in emit-rtl.c
1824 {
1825 MEM_ATTRS (mem) = get_mem_attrs (MEM_ALIAS_SET (mem), MEM_EXPR (mem),
1826 MEM_OFFSET (mem), MEM_SIZE (mem), align,
1827 GET_MODE (mem));
1828 }
宏 MEM_ATTRS 从 rtx 提取 rtmem 域的内容,这个域具有以下定义。
99 typedef struct mem_attrs GTY(()) in rtl.h
100 {
101 HOST_WIDE_INT alias; /* Memory alias set. */
102 tree expr; /* expr corresponding to MEM. */
103 rtx offset; /* Offset from start of DECL, as CONST_INT. */
104 rtx size; /* Size in bytes, as a CONST_INT. */
105 unsigned int align; /* Alignment of MEM in bits. */
106 } mem_attrs;
上面 MEM_ALIAS_SET , MEM_EXPR , MEM_OFFSET 及 MEM_SIZE 提取结构体中相应的域(但如果 rtmem 域是空的返回 0 ,并看到在 make_decl_rtl 的 gen_rtx_MEM 中,一个空指针被置入 rtmem 域)。因此 set_mem_align 的净效果是在 get_mem_attrs 的协助下更新保存对齐量的域。
291 static mem_attrs *
292 get_mem_attrs (HOST_WIDE_INT alias, tree expr, rtx offset, rtx size, in emit-rtl.c
293 unsigned int align, enum machine_mode mode)
294 {
295 mem_attrs attrs;
296 void **slot;
297
298 /* If everything is the default, we can just return zero.
299 This must match what the corresponding MEM_* macros return when the
300 field is not present. */
301 if (alias == 0 && expr == 0 && offset == 0
302 && (size == 0
303 || (mode != BLKmode && GET_MODE_SIZE (mode) == INTVAL (size)))
304 && (STRICT_ALIGNMENT && mode != BLKmode
305 ? align == GET_MODE_ALIGNMENT (mode) : align == BITS_PER_UNIT))
306 return 0;
307
308 attrs.alias = alias;
309 attrs.expr = expr;
310 attrs.offset = offset;
311 attrs.size = size;
312 attrs.align = align;
313
314 slot = htab_find_slot (mem_attrs_htab , &attrs, INSERT);
315 if (*slot == 0)
316 {
317 *slot = ggc_alloc (sizeof (mem_attrs));
318 memcpy (*slot, &attrs, sizeof (mem_attrs));
319 }
320
321 return *slot;
322 }
mem_attrs 的所有实例都被缓存在哈希表 mem_attrs_htab 中以加速查找;并且每个内存属性设置是单件。看到通常地,相同类型的每个声明将具有相同的内存属性设置。
回到 assemble_variable ,在 1458 行, maybe_assemble_visibility 只是处理具有属性“ visibility ”的声明。该函数将调用后端的钩子来输出具有所描述可见性( visibility )的名字。我们在这里跳过其处理,【 6 】对这个属性给出了详细的描述。