看过了转换相关的内容后,回到我们例子的解析后的阶段,从 expand_or_defer_fn 返回,我们回到 cp_parser_function_definition_after_declarator ,并立即返回 FUNCTION_DECL ,这个 FUNCTION_DECL 为 cp_parser_function_definition_from_specifiers_and_declarator 返回,然后 cp_parser_init_declarator , cp_parser_simple_declaration , cp_parser_block_declaration , cp_parser_declaration ,然后在 cp_parser_declaration_seq_opt 中 EOF 突破了 6224 行的 WHILE 循环,最后我们回到了解析器的入口—— cp_parser_translation_unit 。在这个函数中, EOF 在 2336 行被“消化”了,接着 finish_translation_unit 恢复全局名字空间的绑定域。
从 cp_parser_translation_unit 返回,现在我们回到 c_parser_file 。退出它,我们就回到 c_common_parse_file ——对于 C++ ,它被 lang_hooks 的 parse_file 所指向。并且看到 c_parser_file 是在一个 WHILE 循环里;如果在这个命令行中还有其他源文件,上面所看到的过程将再次重复。这意味如果我们使用形如:“ g++ -o all a.cc b.cc c.cc; ”的命令行,编译器将解析这 3 个源文件,并把结果合并到单个中间语言树。在所有的源文件被解析后,就来到 finish_file 。
来的此处,我们一定在全局名字空间中。 2540 行的检查保证了这一点。
2527 void
2528 finish_file (void) in decl2.c
2529 {
2530 tree vars;
2531 bool reconsider;
2532 size_t i;
2533 location_t locus;
2534 unsigned ssdf_count = 0;
2535
2536 locus = input_location ;
2537 at_eof = 1;
2538
2539 /* Bad parse errors. Just forget about it. */
2540 if (! global_bindings_p () || current_class_type || decl_namespace_list )
2541 return ;
2542
2543 if (pch_file )
2544 c_common_write_pch ();
下面, cpp_write_pch_deps 把定义在命令行所指定的源文件中的标识符写入由 pch_outfile 指定的 PCH 文件里。而 asm_file_startpos ,在读入第一个源文件时,在 pch_init 中被设置成指向 asm_out_file 的末尾;而在 c_common_parse_file 的 1242 行的 cpp_read_main_file 中, asm_out_file 被填入被包含的 PCH 文件的内容,并且在 177 行 asm_file_end 指向该文件的末尾。因此在 178 行, h.asm_size 将是被包含的 PCH 文件的内容的大小。这些内容将被增加入这个 PCH 文件。
165 void
166 c_common_write_pch (void) in c-pch.c
167 {
168 char *buf;
169 long asm_file_end;
170 long written;
171 struct c_pch_header h;
172
173 (*debug_hooks ->handle_pch) (1);
174
175 cpp_write_pch_deps (parse_in , pch_outfile );
176
177 asm_file_end = ftell (asm_out_file );
178 h.asm_size = asm_file_end - asm_file_startpos ;
179
180 if (fwrite (&h, sizeof (h), 1, pch_outfile ) != 1)
181 fatal_error ("can't write %s: %m", pch_file );
182
183 buf = xmalloc (16384);
184 fflush (asm_out_file );
185
186 if (fseek (asm_out_file , asm_file_startpos , SEEK_SET) != 0)
187 fatal_error ("can't seek in %s: %m", asm_file_name );
188
189 for (written = asm_file_startpos ; written < asm_file_end; )
190 {
191 long size = asm_file_end - written;
192 if (size > 16384)
193 size = 16384;
194 if (fread (buf, size, 1, asm_out_file ) != 1)
195 fatal_error ("can't read %s: %m", asm_file_name );
196 if (fwrite (buf, size, 1, pch_outfile ) != 1)
197 fatal_error ("can't write %s: %m", pch_file );
198 written += size;
199 }
200 free (buf);
201 /* asm_out_file can be written afterwards, so must be flushed first. */
202 fflush (asm_out_file );
203
204 gt_pch_save (pch_outfile );
205 cpp_write_pch_state (parse_in , pch_outfile );
206
207 if (fseek (pch_outfile , 0, SEEK_SET) != 0
208 || fwrite (get_ident (), IDENT_LENGTH, 1, pch_outfile ) != 1)
209 fatal_error ("can't write %s: %m", pch_file );
210
211 fclose (pch_outfile );
212 }
在上面的 175 行, pch_outfile 是为 pch_file 打开的文件句柄,它也是在 cpp_save_state 中写入使用的文件句柄。现在 r->savedstate 包含了在解析命令行指定源文件前,所出现的标识符。
299 int
300 cpp_write_pch_deps (cpp_reader *r, FILE *f) in cpppch.c
301 {
302 struct macrodef_struct z;
303 struct cpp_savedstate *const ss = r->savedstate;
304 unsigned char *definedstrs;
305 size_t i;
306
307 /* Collect the list of identifiers which have been seen and
308 weren't defined to anything previously. */
309 ss->hashsize = 0;
310 ss->n_defs = 0;
311 cpp_forall_identifiers (r, count_defs , ss);
312
313 ss->defs = xmalloc (ss->n_defs * sizeof (cpp_hashnode *));
314 ss->n_defs = 0;
315 cpp_forall_identifiers (r, write_defs , ss);
316
317 /* Sort the list, copy it into a buffer, and write it out. */
318 qsort (ss->defs, ss->n_defs, sizeof (cpp_hashnode *), &comp_hashnodes);
319 definedstrs = ss->definedstrs = xmalloc (ss->hashsize);
320 for (i = 0; i < ss->n_defs; ++i)
321 {
322 size_t len = NODE_LEN (ss->defs[i]);
323 memcpy (definedstrs, NODE_NAME (ss->defs[i]), len + 1);
324 definedstrs += len + 1;
325 }
326
327 memset (&z, 0, sizeof (z));
328 z.definition_length = ss->hashsize;
329 if (fwrite (&z, sizeof (z), 1, f) != 1
330 || fwrite (ss->definedstrs, ss->hashsize, 1, f) != 1)
331 {
332 cpp_errno (r, CPP_DL_ERROR, "while writing precompiled header");
333 return -1;
334 }
335 free (ss->definedstrs);
336
337 /* Free the saved state. */
338 free (ss);
339 r->savedstate = NULL;
340 return 0;
341 }
在解析了所有在命令行中指定的源文件后, cpp_reader 在其 hash_table 中包含了在这些文件中声明的标识符。那么函数 count_defs 遍历 hash_table 来计算还需要加入多少标识符,并通过域 hashsize 计算保存名字缓存的大小。注意到在中 cpp_save_state 调用的 save_idents 里 NT_VOID 类型的节点被忽略;但这里没有。
209 static int
210 count_defs (cpp_reader *pfile ATTRIBUTE_UNUSED, cpp_hashnode *hn, void *ss_p)
211 {
212 struct cpp_savedstate *const ss = (struct cpp_savedstate *)ss_p;
213
214 switch (hn->type)
215 {
216 case NT_MACRO:
217 if (hn->flags & NODE_BUILTIN)
218 return 1;
219
220 /* else fall through. */
221
222 case NT_VOID:
223 {
224 struct cpp_string news;
225 void **slot;
226
227 news.len = NODE_LEN (hn);
228 news.text = NODE_NAME (hn);
229 slot = htab_find (ss->definedhash, &news);
230 if (slot == NULL)
231 {
232 ss->hashsize += NODE_LEN (hn) + 1;
233 ss->n_defs += 1;
234 }
235 }
236 return 1;
237
238 case NT_ASSERTION:
239 /* Not currently implemented. */
240 return 1;
241
242 default :
243 abort ();
244 }
245 }
在分配了所需的资源后, write_defs 也遍历 cpp_reader 的 hash_table 来援引没有在 cpp_save_state 中出现的标识符。那么在上面 318 行的排序后,这些名字被写入这个 PCH 文件。
248 static int
249 write_defs (cpp_reader *pfile ATTRIBUTE_UNUSED, cpp_hashnode *hn, void *ss_p)
250 {
251 struct cpp_savedstate *const ss = (struct cpp_savedstate *)ss_p;
252
253 switch (hn->type)
254 {
255 case NT_MACRO:
256 if (hn->flags & NODE_BUILTIN)
257 return 1;
258
259 /* else fall through. */
260
261 case NT_VOID:
262 {
263 struct cpp_string news;
264 void **slot;
265
266 news.len = NODE_LEN (hn);
267 news.text = NODE_NAME (hn);
268 slot = htab_find (ss->definedhash, &news);
269 if (slot == NULL)
270 {
271 ss->defs[ss->n_defs] = hn;
272 ss->n_defs += 1;
273 }
274 }
275 return 1;
276
277 case NT_ASSERTION:
278 /* Not currently implemented. */
279 return 1;
280
281 default :
282 abort ();
283 }
284 }
在调用 gt_pch_save 的这一点上, PCH 文件的内容如下图所示。看到 idn 就是用户在源文件中声明的标识符。
另外在编译过程中,编译器还产生许多控制数据,我们已经看到它们由 GC 管理。这些数据描述了程序的特性,并影响代码的生成,因此必须也保存在这个 PCH 文件里。
423 void
424 gt_pch_save (FILE *f) in ggc-common.c
425 {
426 const struct ggc_root_tab *const *rt;
427 const struct ggc_root_tab *rti;
428 size_t i;
429 struct traversal_state state;
430 char *this_object = NULL;
431 size_t this_object_size = 0;
432 struct mmap_info mmi;
433 size_t page_size = getpagesize();
434
435 gt_pch_save_stringpool ();
那么 gt_pch_save_stringpool 保存了 ident_hash 的内容,记得 ident_hash 为 cpp_reader 的 hash_table 所指向。首先,这些内容被拷贝入 spd 数据结构。
232 void
233 gt_pch_save_stringpool (void) in stringpool.c
234 {
235 unsigned int i;
236
237 spd = ggc_alloc (sizeof (*spd ));
238 spd ->nslots = ident_hash ->nslots;
239 spd ->nelements = ident_hash ->nelements;
240 spd ->entries = ggc_alloc (sizeof (tree *) * spd ->nslots);
241 for (i = 0; i < spd ->nslots; i++)
242 if (ident_hash ->entries[i] != NULL)
243 spd->entries[i] = HT_IDENT_TO_GCC_IDENT (ident_hash ->entries[i]);
244 else
245 spd->entries[i] = NULL;
246
247 saved_ident_hash = ht_create (14);
248 saved_ident_hash ->alloc_node = alloc_node;
249 ht_forall (ident_hash , ht_copy_and_clear , saved_ident_hash );
250 }
为了缓存 ident_hash 的内容, spd 具有如下的结构。看到 spd 在 gt_ggc_r_gt_stringpool_h 中,它进而包含在 gt_ggc_rtab 中。后面它也将被写入这个 PCH 文件,而通过 gt_pch_restore_stringpool ,被使用这个 PCH 文件的文件读入。
199 struct string_pool_data GTY(()) in stringpool.c
200 {
201 tree * GTY((length ("%h.nslots"))) entries;
202 unsigned int nslots;
203 unsigned int nelements;
204 };
205
206 static GTY(()) struct string_pool_data * spd;
其次,通过下面的函数把 ident_hash 的内容移入 saved_ident_hash (把它缓存起来,以免对下面的过程产生影响,在退出 gt_pch_save 前会恢复其内容)。
208 static int
209 ht_copy_and_clear (cpp_reader *r ATTRIBUTE_UNUSED, hashnode hp, const void *ht2_p)
210 {
211 cpp_hashnode *h = CPP_HASHNODE (hp);
212 struct ht *ht2 = (struct ht *) ht2_p;
213
214 if (h->type != NT_VOID
215 && (h->flags & NODE_BUILTIN) == 0)
216 {
217 cpp_hashnode *h2 = CPP_HASHNODE (ht_lookup (ht2,
218 NODE_NAME (h),
219 NODE_LEN (h),
220 HT_ALLOC));
221 h2->type = h->type;
222 memcpy (&h2->value, &h->value, sizeof (h->value));
223
224 h->type = NT_VOID;
225 memset (&h->value, 0, sizeof (h->value));
226 }
227 return 1;
228 }
记得在编译器中,所有 GC 管理的对象都由 GC 工具处理来产生 gtype-* 文件。而在 gtype-c.h 中,生成了几个全局数组来保存对这些对象的引用。它们是 gt_ggc_rtab , gt_ggc_deletable_rtab (保存了被删除对象的哈希表), gt_ggc_cache_rtab (用于垃圾收集的目的), gt_pch_cache_rtab (与 gt_ggc_cache_rtab 的内容相同,但处理方法不一样—), gt_pch_scalar_rtab 。
gt_pch_save (continue)
437 saving_htab = htab_create (50000, saving_htab_hash, saving_htab_eq, free);
438
439 for (rt = gt_ggc_rtab ; *rt; rt++)
440 for (rti = *rt; rti->base != NULL; rti++)
441 for (i = 0; i < rti->nelt; i++)
442 (*rti->pchw)(*(void **)((char *)rti->base + rti->stride * i));
443
444 for (rt = gt_pch_cache_rtab ; *rt; rt++)
445 for (rti = *rt; rti->base != NULL; rti++)
446 for (i = 0; i < rti->nelt; i++)
447 (*rti->pchw)(*(void **)((char *)rti->base + rti->stride * i));
448
449 /* Prepare the objects for writing, determine addresses and such. */
450 state.f = f;
451 state.d = init_ggc_pch();
452 state.count = 0;
453 htab_traverse (saving_htab , call_count , &state);
PCH 文件中的内容是中间形式的代码(也就是树节点的形式),我们已经看到这个中间形式中包含了大量的指针,如何在 PCH 文件中维护这些指针,使得在 PCH 文件的使用中能恢复这里的对象之间的关系?
首先,由 GC 管理的对象都是全局对象,而且它们构成了一个封闭的集合(也就是不会有对集合外对象的引用,也不会被集合外的对象所引用)。那么如果把这些对象的内容及地址一字不差地记录入 PCH 文件,然后读入时一个个恢复这些对象,理论上就能恢复 PCH 文件的内容。但事实上不可行,因为读入 PCH 文件时,内存中很可能已经有其他内容,很难保证需要的地址一定可用,而且一个个恢复对象效率也不高。
既然 GC 管理的是由全局对象构成的封闭集合,那么在目标系统所提供的内存管理机制的协助下,存在这种可能:把所有的对象尽可能紧凑地排列起来,并为以后的 PCH 读入选定一个不太可能被占用的起始地址(对于使用虚拟内存的系统,不占用的可能性很大),同时把对象内部的所有指针都更新为相应对象的新的地址(假定起始地址可用);最后把这个映射信息、更新后的对象内容写入 PCH 文件,就能保证后面对这个 PCH 文件的正确读入。这就是 GCC 现在的做法。
那么第一步就要收集对象中所有的指针的内容(包括对象自己的地址),这由 pchw 域引用的函数来完成,它把这些内容存入 saving_htab 。以 gt_ggc_rtab 中 gt_ggc_r_gtype_desc_c 的 cgraph_nodes_queue 为例,其 pchw 指向 gt_pch_nx_cgraph_node ,这个函数也是由 GC 工具产生。
1306 void
1307 gt_pch_nx_cgraph_node (void *x_p) in gtype-desc.c
1308 {
1309 struct cgraph_node * x = (struct cgraph_node *)x_p;
1310 struct cgraph_node * xlimit = x;
1311 while (gt_pch_note_object (xlimit, xlimit, gt_pch_p_11cgraph_node ))
1312 xlimit = ((*xlimit).next);
1313 if (x != xlimit)
1314 for (;;)
1315 {
1316 struct cgraph_node * const xprev = ((*x).previous);
1317 if (xprev == NULL) break ;
1318 x = xprev;
1319 (void) gt_pch_note_object (xprev, xprev, gt_pch_p_11cgraph_node );
1320 }
1321 while (x != xlimit)
1322 {
1323 gt_pch_n_9tree_node ((*x).decl);
1324 gt_pch_n_11cgraph_edge ((*x).callees);
1325 gt_pch_n_11cgraph_edge ((*x).callers);
1326 gt_pch_n_11cgraph_node ((*x).next);
1327 gt_pch_n_11cgraph_node ((*x).previous);
1328 gt_pch_n_11cgraph_node ((*x).origin);
1329 gt_pch_n_11cgraph_node ((*x).nested);
1330 gt_pch_n_11cgraph_node ((*x).next_nested);
1331 gt_pch_n_11cgraph_node ((*x).next_needed);
1332 x = ((*x).next);
1333 }
1334 }
这里关键的函数是 gt_pch_note_object ,上面的函数 gt_pch_n_* 也调用 gt_pch_note_object 来缓存它们所处理的节点。
251 int
252 gt_pch_note_object (void *obj, void *note_ptr_cookie, in ggc-common.c
253 gt_note_pointers note_ptr_fn)
254 {
255 struct ptr_data **slot;
256
257 if (obj == NULL || obj == (void *) 1)
258 return 0;
259
260 slot = (struct ptr_data **)
261 htab_find_slot_with_hash (saving_htab , obj, POINTER_HASH (obj),
262 INSERT);
263 if (*slot != NULL)
264 {
265 if ((*slot)->note_ptr_fn != note_ptr_fn
266 || (*slot)->note_ptr_cookie != note_ptr_cookie)
267 abort ();
268 return 0;
269 }
270
271 *slot = xcalloc (sizeof (struct ptr_data), 1);
272 (*slot)->obj = obj;
273 (*slot)->note_ptr_fn = note_ptr_fn;
274 (*slot)->note_ptr_cookie = note_ptr_cookie;
275 if (note_ptr_fn == gt_pch_p_S)
276 (*slot)->size = strlen (obj) + 1;
277 else
278 (*slot)->size = ggc_get_size (obj);
279 return 1;
280 }
saving_htab 的内容具有下面的类型 ptr_data 。结构中的域 obj 保存了被缓存对象的地址。
237 struct ptr_data in ggc-common.c
238 {
239 void *obj;
240 void *note_ptr_cookie;
241 gt_note_pointers note_ptr_fn;
242 gt_handle_reorder reorder_fn;
243 size_t size;
244 void *new_addr;
245 };
那么在 453 行, htab_traverse 遍历 saving_htab 并为其中的每个节点调用 call_count 。看到 state 的 count 域记录了所遭遇的对象的个数。
328 static int
329 call_count (void **slot, void *state_p) in ggc-common.c
330 {
331 struct ptr_data *d = (struct ptr_data *)*slot;
332 struct traversal_state *state = (struct traversal_state *)state_p;
333
334 ggc_pch_count_object (state->d, d->obj, d->size, d->note_ptr_fn == gt_pch_p_S);
335 state->count++;
336 return 1;
337 }
在以 Linux 为目标平台时, ggc-page.c 将被选中提供内存管理。定义在这个文件中的函数 ggc_pch_count_object 协助统计具有指定大小的对象的数目;下面的 order 表示以 2 为底 order 为指数的大小(它将是页面管理机制下所分配的内存大小)。
1941 void
1942 ggc_pch_count_object (struct ggc_pch_data *d, void *x ATTRIBUTE_UNUSED,
1943 size_t size, bool is_string ATTRIBUTE_UNUSED)
1944 {
1945 unsigned order;
1946
1947 if (size <= 256)
1948 order = size_lookup [size];
1949 else
1950 {
1951 order = 9;
1952 while (size > OBJECT_SIZE (order))
1953 order++;
1954 }
1955
1956 d->d.totals[order]++;
1957 }
下面将选定新的起始地址,它将为读入过程所使用。
gt_pch_save (continue)
455 mmi.size = ggc_pch_total_size (state.d);
456
457 /* Try to arrange things so that no relocation is necessary, but
458 don't try very hard. On most platforms, this will always work,
459 and on the rest it's a lot of work to do better.
460 (The extra work goes in HOST_HOOKS_GT_PCH_GET_ADDRESS and
461 HOST_HOOKS_GT_PCH_USE_ADDRESS.) */
462 mmi.preferred_base = host_hooks .gt_pch_get_address (mmi.size, fileno (f));
463
464 ggc_pch_this_base (state.d, mmi.preferred_base);
465
466 state.ptrs = xmalloc (state.count * sizeof (*state.ptrs));
467 state.ptrs_i = 0;
468 htab_traverse (saving_htab , call_alloc , &state);
469 qsort (state.ptrs, state.count, sizeof (*state.ptrs), compare_ptr_data);
470
471 /* Write out all the scalar variables. */
472 for (rt = gt_pch_scalar_rtab ; *rt; rt++)
473 for (rti = *rt; rti->base != NULL; rti++)
474 if (fwrite (rti->base, rti->stride, 1, f) != 1)
475 fatal_error ("can't write PCH file: %m");
首先需要知道所需要的整个内存的大小。这个尺寸被取整到最近的页边界。
1959 size_t
1960 ggc_pch_total_size (struct ggc_pch_data *d) in ggc-page.c
1961 {
1962 size_t a = 0;
1963 unsigned i;
1964
1965 for (i = 0; i < NUM_ORDERS; i++)
1966 a += ROUND_UP (d->d.totals[i] * OBJECT_SIZE (i), G .pagesize);
1967 return a;
1968 }
另外对象被按尺寸已升序处理,所有具有相同分配大小的对象将被放在一起;下面结构的 base 域给出这些对象的起始地址。
1925 struct ggc_pch_data in ggc-page.c
1926 {
1927 struct ggc_pch_ondisk
1928 {
1929 unsigned totals[NUM_ORDERS];
1930 } d;
1931 size_t base[NUM_ORDERS];
1932 size_t written[NUM_ORDERS];
1933 };
在分页机制中,所有的基址都应该被取整到页边界。看到每个不同大小的对象组都有自己的基址。
1970 void
1971 ggc_pch_this_base (struct ggc_pch_data *d, void *base) in ggc-page.c
1972 {
1973 size_t a = (size_t) base;
1974 unsigned i;
1975
1976 for (i = 0; i < NUM_ORDERS; i++)
1977 {
1978 d->base[i] = a;
1979 a += ROUND_UP (d->d.totals[i] * OBJECT_SIZE (i), G .pagesize);
1980 }
1981 }
以上面的信息,尤其是对象的基址,在 saving_htab 中被缓存的对象由 call_alloc 来重新安排。注意参数 slot 就是来自 saving_htab 的对象。
339 static int
340 call_alloc (void **slot, void *state_p) in ggc-common.c
341 {
342 struct ptr_data *d = (struct ptr_data *)*slot;
343 struct traversal_state *state = (struct traversal_state *)state_p;
344
345 d->new_addr = ggc_pch_alloc_object (state->d, d->obj, d->size, d->note_ptr_fn == gt_pch_p_S);
346 state->ptrs[state->ptrs_i++] = d;
347 return 1;
348 }
在 345 行, new_addr 保存了重新安排后对象的地址,而在下面的函数中, d 的 base 域指向下一个对象可用的地址,它是内存分配的应该彩排,没有真正分配内存。
1984 char *
1985 ggc_pch_alloc_object (struct ggc_pch_data *d, void *x ATTRIBUTE_UNUSED, in ggc-page.c
1986 size_t size, bool is_string ATTRIBUTE_UNUSED)
1987 {
1988 unsigned order;
1989 char *result;
1990
1991 if (size <= 256)
1992 order = size_lookup [size];
1993 else
1994 {
1995 order = 9;
1996 while (size > OBJECT_SIZE (order))
1997 order++;
1998 }
1999
2000 result = (char *) d->base[order];
2001 d->base[order] += OBJECT_SIZE (order);
2002 return result;
2003 }
现在在下面的 state 里,其 ptrs 数组指向对象,并且对于每个实体, new_addr 域记录了指定对象的重新映射的地址。
gt_pch_save (continue)
477 /* Write out all the global pointers, after translation. */
478 write_pch_globals (gt_ggc_rtab , &state);
479 write_pch_globals (gt_pch_cache_rtab , &state);
看到 write_pch_globals 把这些对象的新地址写入 PCH 文件。
381 static void
382 write_pch_globals (const struct ggc_root_tab * const *tab, in ggc-common.c
383 struct traversal_state *state)
384 {
385 const struct ggc_root_tab *const *rt;
386 const struct ggc_root_tab *rti;
387 size_t i;
388
389 for (rt = tab; *rt; rt++)
390 for (rti = *rt; rti->base != NULL; rti++)
391 for (i = 0; i < rti->nelt; i++)
392 {
393 void *ptr = *(void **)((char *)rti->base + rti->stride * i);
394 struct ptr_data *new_ptr;
395 if (ptr == NULL || ptr == (void *)1)
396 {
397 if (fwrite (&ptr, sizeof (void *), 1, state->f)
398 != 1)
399 fatal_error ("can't write PCH file: %m");
400 }
401 else
402 {
403 new_ptr = htab_find_with_hash (saving_htab , ptr,
404 POINTER_HASH (ptr));
405 if (fwrite (&new_ptr->new_addr, sizeof (void *), 1, state->f)
406 != 1)
407 fatal_error ("can't write PCH file: %m");
408 }
409 }
410 }
在这之后就是在读入时需要进行正确映射的部分,只有下面的内容得到正确映射,上面所保存的地址才有意义。接下来的部分,除去 mmi 的结构,余下部分必须在页边界上对齐(在它和 mmi 之间填 0 ),这样在提供 mmap 系统调用的目标操作系统上就能直接映射这些内容。
gt_pch_save (continue)
481 ggc_pch_prepare_write (state.d, state.f);
482
483 /* Pad the PCH file so that the mmapped area starts on a page boundary. */
484 {
485 long o;
486 o = ftell (state.f) + sizeof (mmi);
487 if (o == -1)
488 fatal_error ("can't get position in PCH file: %m");
489 mmi.offset = page_size - o % page_size;
490 if (mmi.offset == page_size)
491 mmi.offset = 0;
492 mmi.offset += o;
493 }
494 if (fwrite (&mmi, sizeof (mmi), 1, state.f) != 1)
495 fatal_error ("can't write PCH file: %m");
496 if (mmi.offset != 0
497 && fseek (state.f, mmi.offset, SEEK_SET) != 0)
498 fatal_error ("can't write padding to PCH file: %m");
499
500 /* Actually write out the objects. */
501 for (i = 0; i < state.count; i++)
502 {
503 if (this_object_size < state.ptrs[i]->size)
504 {
505 this_object_size = state.ptrs[i]->size;
506 this_object = xrealloc (this_object, this_object_size);
507 }
508 memcpy (this_object, state.ptrs[i]->obj, state.ptrs[i]->size);
509 if (state.ptrs[i]->reorder_fn != NULL)
510 state.ptrs[i]->reorder_fn (state.ptrs[i]->obj,
511 state.ptrs[i]->note_ptr_cookie,
512 relocate_ptrs, &state);
513 state.ptrs[i]->note_ptr_fn (state.ptrs[i]->obj,
514 state.ptrs[i]->note_ptr_cookie,
515 relocate_ptrs , &state);
516 ggc_pch_write_object (state.d, state.f, state.ptrs[i]->obj,
517 state.ptrs[i]->new_addr, state.ptrs[i]->size,
518 state.ptrs[i]->note_ptr_fn == gt_pch_p_S);
519 if (state.ptrs[i]->note_ptr_fn != gt_pch_p_S)
520 memcpy (state.ptrs[i]->obj, this_object, state.ptrs[i]->size);
521 }
522 ggc_pch_finish (state.d, state.f);
523 gt_pch_fixup_stringpool ();
524
525 free (state.ptrs);
526 htab_delete (saving_htab );
527 }
那么 501 行的 FOR 循环把内容更新后的 GC 管理对象写入文件。注意到它们的内容必须被更新为新地址。在数组 ptrs 中保存的对象由 gt_pch_note_object 分配而来,它们具有空的 reorder_fn ,不过填好了 note_ptr_fn 。以 cgraph_nodes_queue 为例,其 note_ptr_fn 指向 gt_pch_p_11cgraph_node 。
2625 void
2626 gt_pch_p_11cgraph_node (void *this_obj ATTRIBUTE_UNUSED, in ggc-desc.c
2627 void *x_p,
2628 gt_pointer_operator op ATTRIBUTE_UNUSED,
2629 void *cookie ATTRIBUTE_UNUSED)
2630 {
2631 struct cgraph_node * const x ATTRIBUTE_UNUSED = (struct cgraph_node *)x_p;
2632 if ((void *)(x) == this_obj)
2633 op (&((*x).decl), cookie);
2634 if ((void *)(x) == this_obj)
2635 op (&((*x).callees), cookie);
2636 if ((void *)(x) == this_obj)
2637 op (&((*x).callers), cookie);
2638 if ((void *)(x) == this_obj)
2639 op (&((*x).next), cookie);
2640 if ((void *)(x) == this_obj)
2641 op (&((*x).previous), cookie);
2642 if ((void *)(x) == this_obj)
2643 op (&((*x).origin), cookie);
2644 if ((void *)(x) == this_obj)
2645 op (&((*x).nested), cookie);
2646 if ((void *)(x) == this_obj)
2647 op (&((*x).next_nested), cookie);
2648 if ((void *)(x) == this_obj)
2649 op (&((*x).next_needed), cookie);
2650 }
该函数调用了由 op 引用的函数来相应地更新对象中保存地址的域。通常 op 指向 relocate_ptrs 。
363 static void
364 relocate_ptrs (void *ptr_p, void *state_p) in ggc-common.c
365 {
366 void **ptr = (void **)ptr_p;
367 struct traversal_state *state ATTRIBUTE_UNUSED
368 = (struct traversal_state *)state_p;
369 struct ptr_data *result;
370
371 if (*ptr == NULL || *ptr == (void *)1)
372 return ;
373
374 result = htab_find_with_hash (saving_htab , *ptr, POINTER_HASH (*ptr));
375 if (result == NULL)
376 abort ();
377 *ptr = result->new_addr;
378 }
在 516 行, ggc_pch_write_object 把更新后的对象写入文件。然后 ggc_pch_finish 释放 state 中的 d 。并且 ident_hash 为 gt_pch_fixup_stringpool 所复原。
252 void
253 gt_pch_fixup_stringpool (void) in stringpool.c
254 {
255 ht_forall (saved_ident_hash , ht_copy_and_clear , ident_hash );
256 ht_destroy (saved_ident_hash );
257 saved_ident_hash = 0;
258 }
PCH 文件的最后部分是其源文件中的宏定义,及由 –MT 或 –MQ 选项所携带的依赖信息。
364 int
365 cpp_write_pch_state (cpp_reader *r, FILE *f) in cpppch.c
366 {
367 struct macrodef_struct z;
368
369 /* Write out the list of defined identifiers. */
370 cpp_forall_identifiers (r, write_macdef , f);
371 memset (&z, 0, sizeof (z));
372 if (fwrite (&z, sizeof (z), 1, f) != 1)
373 {
374 cpp_errno (r, CPP_DL_ERROR, "while writing precompiled header");
375 return -1;
376 }
377
378 if (!r->deps)
379 r->deps = deps_init ();
380
381 if (deps_save (r->deps, f) != 0)
382 {
383 cpp_errno (r, CPP_DL_ERROR, "while writing precompiled header");
384 return -1;
385 }
386
387 return 0;
388 }
最后,一个 PCH 文件的内容显示如下。
图 118 : PCH 文件内容