File: | build/gcc/ggc-common.cc |
Warning: | line 636, column 7 Null pointer passed to 1st parameter expecting 'nonnull' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* Simple garbage collection for the GNU compiler. | |||
2 | Copyright (C) 1999-2023 Free Software Foundation, Inc. | |||
3 | ||||
4 | This file is part of GCC. | |||
5 | ||||
6 | GCC is free software; you can redistribute it and/or modify it under | |||
7 | the terms of the GNU General Public License as published by the Free | |||
8 | Software Foundation; either version 3, or (at your option) any later | |||
9 | version. | |||
10 | ||||
11 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |||
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | |||
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |||
14 | for more details. | |||
15 | ||||
16 | You should have received a copy of the GNU General Public License | |||
17 | along with GCC; see the file COPYING3. If not see | |||
18 | <http://www.gnu.org/licenses/>. */ | |||
19 | ||||
20 | /* Generic garbage collection (GC) functions and data, not specific to | |||
21 | any particular GC implementation. */ | |||
22 | ||||
23 | #include "config.h" | |||
24 | #define INCLUDE_MALLOC_H | |||
25 | #include "system.h" | |||
26 | #include "coretypes.h" | |||
27 | #include "timevar.h" | |||
28 | #include "diagnostic-core.h" | |||
29 | #include "ggc-internal.h" | |||
30 | #include "hosthooks.h" | |||
31 | #include "plugin.h" | |||
32 | #include "options.h" | |||
33 | ||||
34 | /* When true, protect the contents of the identifier hash table. */ | |||
35 | bool ggc_protect_identifiers = true; | |||
36 | ||||
37 | /* Statistics about the allocation. */ | |||
38 | static ggc_statistics *ggc_stats; | |||
39 | ||||
40 | struct traversal_state; | |||
41 | ||||
42 | static int compare_ptr_data (const void *, const void *); | |||
43 | static void relocate_ptrs (void *, void *, void *); | |||
44 | static void write_pch_globals (const struct ggc_root_tab * const *tab, | |||
45 | struct traversal_state *state); | |||
46 | ||||
47 | /* Maintain global roots that are preserved during GC. */ | |||
48 | ||||
49 | /* This extra vector of dynamically registered root_tab-s is used by | |||
50 | ggc_mark_roots and gives the ability to dynamically add new GGC root | |||
51 | tables, for instance from some plugins; this vector is on the heap | |||
52 | since it is used by GGC internally. */ | |||
53 | typedef const struct ggc_root_tab *const_ggc_root_tab_t; | |||
54 | static vec<const_ggc_root_tab_t> extra_root_vec; | |||
55 | ||||
56 | /* Dynamically register a new GGC root table RT. This is useful for | |||
57 | plugins. */ | |||
58 | ||||
59 | void | |||
60 | ggc_register_root_tab (const struct ggc_root_tab* rt) | |||
61 | { | |||
62 | if (rt) | |||
63 | extra_root_vec.safe_push (rt); | |||
64 | } | |||
65 | ||||
66 | /* Mark all the roots in the table RT. */ | |||
67 | ||||
68 | static void | |||
69 | ggc_mark_root_tab (const_ggc_root_tab_t rt) | |||
70 | { | |||
71 | size_t i; | |||
72 | ||||
73 | for ( ; rt->base != NULLnullptr; rt++) | |||
74 | for (i = 0; i < rt->nelt; i++) | |||
75 | (*rt->cb) (*(void **) ((char *)rt->base + rt->stride * i)); | |||
76 | } | |||
77 | ||||
78 | /* Iterate through all registered roots and mark each element. */ | |||
79 | ||||
80 | void | |||
81 | ggc_mark_roots (void) | |||
82 | { | |||
83 | const struct ggc_root_tab *const *rt; | |||
84 | const_ggc_root_tab_t rtp, rti; | |||
85 | size_t i; | |||
86 | ||||
87 | for (rt = gt_ggc_deletable_rtab; *rt; rt++) | |||
88 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
89 | memset (rti->base, 0, rti->stride); | |||
90 | ||||
91 | for (rt = gt_ggc_rtab; *rt; rt++) | |||
92 | ggc_mark_root_tab (*rt); | |||
93 | ||||
94 | FOR_EACH_VEC_ELT (extra_root_vec, i, rtp)for (i = 0; (extra_root_vec).iterate ((i), &(rtp)); ++(i) ) | |||
95 | ggc_mark_root_tab (rtp); | |||
96 | ||||
97 | if (ggc_protect_identifiers) | |||
98 | ggc_mark_stringpool (); | |||
99 | ||||
100 | gt_clear_caches (); | |||
101 | ||||
102 | if (! ggc_protect_identifiers) | |||
103 | ggc_purge_stringpool (); | |||
104 | ||||
105 | /* Some plugins may call ggc_set_mark from here. */ | |||
106 | invoke_plugin_callbacks (PLUGIN_GGC_MARKING, NULLnullptr); | |||
107 | } | |||
108 | ||||
109 | /* Allocate a block of memory, then clear it. */ | |||
110 | void * | |||
111 | ggc_internal_cleared_alloc (size_t size, void (*f)(void *), size_t s, size_t n | |||
112 | MEM_STAT_DECL) | |||
113 | { | |||
114 | void *buf = ggc_internal_alloc (size, f, s, n PASS_MEM_STAT); | |||
115 | memset (buf, 0, size); | |||
116 | return buf; | |||
117 | } | |||
118 | ||||
119 | /* Resize a block of memory, possibly re-allocating it. */ | |||
120 | void * | |||
121 | ggc_realloc (void *x, size_t size MEM_STAT_DECL) | |||
122 | { | |||
123 | void *r; | |||
124 | size_t old_size; | |||
125 | ||||
126 | if (x == NULLnullptr) | |||
127 | return ggc_internal_alloc (size PASS_MEM_STAT); | |||
128 | ||||
129 | old_size = ggc_get_size (x); | |||
130 | ||||
131 | if (size <= old_size) | |||
132 | { | |||
133 | /* Mark the unwanted memory as unaccessible. We also need to make | |||
134 | the "new" size accessible, since ggc_get_size returns the size of | |||
135 | the pool, not the size of the individually allocated object, the | |||
136 | size which was previously made accessible. Unfortunately, we | |||
137 | don't know that previously allocated size. Without that | |||
138 | knowledge we have to lose some initialization-tracking for the | |||
139 | old parts of the object. An alternative is to mark the whole | |||
140 | old_size as reachable, but that would lose tracking of writes | |||
141 | after the end of the object (by small offsets). Discard the | |||
142 | handle to avoid handle leak. */ | |||
143 | VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS ((char *) x + size, | |||
144 | old_size - size)); | |||
145 | VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (x, size)); | |||
146 | return x; | |||
147 | } | |||
148 | ||||
149 | r = ggc_internal_alloc (size PASS_MEM_STAT); | |||
150 | ||||
151 | /* Since ggc_get_size returns the size of the pool, not the size of the | |||
152 | individually allocated object, we'd access parts of the old object | |||
153 | that were marked invalid with the memcpy below. We lose a bit of the | |||
154 | initialization-tracking since some of it may be uninitialized. */ | |||
155 | VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (x, old_size)); | |||
156 | ||||
157 | memcpy (r, x, old_size); | |||
158 | ||||
159 | /* The old object is not supposed to be used anymore. */ | |||
160 | ggc_free (x); | |||
161 | ||||
162 | return r; | |||
163 | } | |||
164 | ||||
165 | void * | |||
166 | ggc_cleared_alloc_htab_ignore_args (size_t c ATTRIBUTE_UNUSED__attribute__ ((__unused__)), | |||
167 | size_t n ATTRIBUTE_UNUSED__attribute__ ((__unused__))) | |||
168 | { | |||
169 | gcc_assert (c * n == sizeof (struct htab))((void)(!(c * n == sizeof (struct htab)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 169, __FUNCTION__), 0 : 0)); | |||
170 | return ggc_cleared_alloc<htab> (); | |||
171 | } | |||
172 | ||||
173 | /* TODO: once we actually use type information in GGC, create a new tag | |||
174 | gt_gcc_ptr_array and use it for pointer arrays. */ | |||
175 | void * | |||
176 | ggc_cleared_alloc_ptr_array_two_args (size_t c, size_t n) | |||
177 | { | |||
178 | gcc_assert (sizeof (void **) == n)((void)(!(sizeof (void **) == n) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 178, __FUNCTION__), 0 : 0)); | |||
179 | return ggc_cleared_vec_alloc<void **> (c); | |||
180 | } | |||
181 | ||||
182 | /* These are for splay_tree_new_ggc. */ | |||
183 | void * | |||
184 | ggc_splay_alloc (int sz, void *nl) | |||
185 | { | |||
186 | gcc_assert (!nl)((void)(!(!nl) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 186, __FUNCTION__), 0 : 0)); | |||
187 | return ggc_internal_alloc (sz); | |||
188 | } | |||
189 | ||||
190 | void | |||
191 | ggc_splay_dont_free (void * x ATTRIBUTE_UNUSED__attribute__ ((__unused__)), void *nl) | |||
192 | { | |||
193 | gcc_assert (!nl)((void)(!(!nl) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 193, __FUNCTION__), 0 : 0)); | |||
194 | } | |||
195 | ||||
196 | void | |||
197 | ggc_print_common_statistics (FILE *stream ATTRIBUTE_UNUSED__attribute__ ((__unused__)), | |||
198 | ggc_statistics *stats) | |||
199 | { | |||
200 | /* Set the pointer so that during collection we will actually gather | |||
201 | the statistics. */ | |||
202 | ggc_stats = stats; | |||
203 | ||||
204 | /* Then do one collection to fill in the statistics. */ | |||
205 | ggc_collect (); | |||
206 | ||||
207 | /* At present, we don't really gather any interesting statistics. */ | |||
208 | ||||
209 | /* Don't gather statistics any more. */ | |||
210 | ggc_stats = NULLnullptr; | |||
211 | } | |||
212 | ||||
213 | /* Functions for saving and restoring GCable memory to disk. */ | |||
214 | ||||
215 | struct ptr_data | |||
216 | { | |||
217 | void *obj; | |||
218 | void *note_ptr_cookie; | |||
219 | gt_note_pointers note_ptr_fn; | |||
220 | gt_handle_reorder reorder_fn; | |||
221 | size_t size; | |||
222 | void *new_addr; | |||
223 | }; | |||
224 | ||||
225 | #define POINTER_HASH(x)(hashval_t)((intptr_t)x >> 3) (hashval_t)((intptr_t)x >> 3) | |||
226 | ||||
227 | /* Helper for hashing saving_htab. */ | |||
228 | ||||
229 | struct saving_hasher : free_ptr_hash <ptr_data> | |||
230 | { | |||
231 | typedef void *compare_type; | |||
232 | static inline hashval_t hash (const ptr_data *); | |||
233 | static inline bool equal (const ptr_data *, const void *); | |||
234 | }; | |||
235 | ||||
236 | inline hashval_t | |||
237 | saving_hasher::hash (const ptr_data *p) | |||
238 | { | |||
239 | return POINTER_HASH (p->obj)(hashval_t)((intptr_t)p->obj >> 3); | |||
240 | } | |||
241 | ||||
242 | inline bool | |||
243 | saving_hasher::equal (const ptr_data *p1, const void *p2) | |||
244 | { | |||
245 | return p1->obj == p2; | |||
246 | } | |||
247 | ||||
248 | static hash_table<saving_hasher> *saving_htab; | |||
249 | static vec<void *> callback_vec; | |||
250 | static vec<void *> reloc_addrs_vec; | |||
251 | ||||
252 | /* Register an object in the hash table. */ | |||
253 | ||||
254 | int | |||
255 | gt_pch_note_object (void *obj, void *note_ptr_cookie, | |||
256 | gt_note_pointers note_ptr_fn, | |||
257 | size_t length_override) | |||
258 | { | |||
259 | struct ptr_data **slot; | |||
260 | ||||
261 | if (obj == NULLnullptr || obj == (void *) 1) | |||
262 | return 0; | |||
263 | ||||
264 | slot = (struct ptr_data **) | |||
265 | saving_htab->find_slot_with_hash (obj, POINTER_HASH (obj)(hashval_t)((intptr_t)obj >> 3), INSERT); | |||
266 | if (*slot != NULLnullptr) | |||
267 | { | |||
268 | gcc_assert ((*slot)->note_ptr_fn == note_ptr_fn((void)(!((*slot)->note_ptr_fn == note_ptr_fn && ( *slot)->note_ptr_cookie == note_ptr_cookie) ? fancy_abort ( "/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 269, __FUNCTION__), 0 : 0)) | |||
269 | && (*slot)->note_ptr_cookie == note_ptr_cookie)((void)(!((*slot)->note_ptr_fn == note_ptr_fn && ( *slot)->note_ptr_cookie == note_ptr_cookie) ? fancy_abort ( "/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 269, __FUNCTION__), 0 : 0)); | |||
270 | return 0; | |||
271 | } | |||
272 | ||||
273 | *slot = XCNEW (struct ptr_data)((struct ptr_data *) xcalloc (1, sizeof (struct ptr_data))); | |||
274 | (*slot)->obj = obj; | |||
275 | (*slot)->note_ptr_fn = note_ptr_fn; | |||
276 | (*slot)->note_ptr_cookie = note_ptr_cookie; | |||
277 | if (length_override != (size_t)-1) | |||
278 | (*slot)->size = length_override; | |||
279 | else if (note_ptr_fn == gt_pch_p_S) | |||
280 | (*slot)->size = strlen ((const char *)obj) + 1; | |||
281 | else | |||
282 | (*slot)->size = ggc_get_size (obj); | |||
283 | return 1; | |||
284 | } | |||
285 | ||||
286 | /* Register address of a callback pointer. */ | |||
287 | void | |||
288 | gt_pch_note_callback (void *obj, void *base) | |||
289 | { | |||
290 | void *ptr; | |||
291 | memcpy (&ptr, obj, sizeof (void *)); | |||
292 | if (ptr != NULLnullptr) | |||
293 | { | |||
294 | struct ptr_data *data | |||
295 | = (struct ptr_data *) | |||
296 | saving_htab->find_with_hash (base, POINTER_HASH (base)(hashval_t)((intptr_t)base >> 3)); | |||
297 | gcc_assert (data)((void)(!(data) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 297, __FUNCTION__), 0 : 0)); | |||
298 | callback_vec.safe_push ((char *) data->new_addr | |||
299 | + ((char *) obj - (char *) base)); | |||
300 | } | |||
301 | } | |||
302 | ||||
303 | /* Register an object in the hash table. */ | |||
304 | ||||
305 | void | |||
306 | gt_pch_note_reorder (void *obj, void *note_ptr_cookie, | |||
307 | gt_handle_reorder reorder_fn) | |||
308 | { | |||
309 | struct ptr_data *data; | |||
310 | ||||
311 | if (obj == NULLnullptr || obj == (void *) 1) | |||
312 | return; | |||
313 | ||||
314 | data = (struct ptr_data *) | |||
315 | saving_htab->find_with_hash (obj, POINTER_HASH (obj)(hashval_t)((intptr_t)obj >> 3)); | |||
316 | gcc_assert (data && data->note_ptr_cookie == note_ptr_cookie)((void)(!(data && data->note_ptr_cookie == note_ptr_cookie ) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 316, __FUNCTION__), 0 : 0)); | |||
317 | ||||
318 | data->reorder_fn = reorder_fn; | |||
319 | } | |||
320 | ||||
321 | /* Handy state for the traversal functions. */ | |||
322 | ||||
323 | struct traversal_state | |||
324 | { | |||
325 | FILE *f; | |||
326 | struct ggc_pch_data *d; | |||
327 | size_t count; | |||
328 | struct ptr_data **ptrs; | |||
329 | size_t ptrs_i; | |||
330 | }; | |||
331 | ||||
332 | /* Callbacks for htab_traverse. */ | |||
333 | ||||
334 | int | |||
335 | ggc_call_count (ptr_data **slot, traversal_state *state) | |||
336 | { | |||
337 | struct ptr_data *d = *slot; | |||
338 | ||||
339 | ggc_pch_count_object (state->d, d->obj, d->size, | |||
340 | d->note_ptr_fn == gt_pch_p_S); | |||
341 | state->count++; | |||
342 | return 1; | |||
343 | } | |||
344 | ||||
345 | int | |||
346 | ggc_call_alloc (ptr_data **slot, traversal_state *state) | |||
347 | { | |||
348 | struct ptr_data *d = *slot; | |||
349 | ||||
350 | d->new_addr = ggc_pch_alloc_object (state->d, d->obj, d->size, | |||
351 | d->note_ptr_fn == gt_pch_p_S); | |||
352 | state->ptrs[state->ptrs_i++] = d; | |||
353 | return 1; | |||
354 | } | |||
355 | ||||
356 | /* Callback for qsort. */ | |||
357 | ||||
358 | static int | |||
359 | compare_ptr_data (const void *p1_p, const void *p2_p) | |||
360 | { | |||
361 | const struct ptr_data *const p1 = *(const struct ptr_data *const *)p1_p; | |||
362 | const struct ptr_data *const p2 = *(const struct ptr_data *const *)p2_p; | |||
363 | return (((size_t)p1->new_addr > (size_t)p2->new_addr) | |||
364 | - ((size_t)p1->new_addr < (size_t)p2->new_addr)); | |||
365 | } | |||
366 | ||||
367 | /* Callbacks for note_ptr_fn. */ | |||
368 | ||||
369 | static void | |||
370 | relocate_ptrs (void *ptr_p, void *real_ptr_p, void *state_p) | |||
371 | { | |||
372 | void **ptr = (void **)ptr_p; | |||
373 | struct traversal_state *state | |||
374 | = (struct traversal_state *)state_p; | |||
375 | struct ptr_data *result; | |||
376 | ||||
377 | if (*ptr == NULLnullptr || *ptr == (void *)1) | |||
378 | return; | |||
379 | ||||
380 | result = (struct ptr_data *) | |||
381 | saving_htab->find_with_hash (*ptr, POINTER_HASH (*ptr)(hashval_t)((intptr_t)*ptr >> 3)); | |||
382 | gcc_assert (result)((void)(!(result) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 382, __FUNCTION__), 0 : 0)); | |||
383 | *ptr = result->new_addr; | |||
384 | if (ptr_p == real_ptr_p) | |||
385 | return; | |||
386 | if (real_ptr_p == NULLnullptr) | |||
387 | real_ptr_p = ptr_p; | |||
388 | gcc_assert (real_ptr_p >= state->ptrs[state->ptrs_i]->obj((void)(!(real_ptr_p >= state->ptrs[state->ptrs_i]-> obj && ((char *) real_ptr_p + sizeof (void *) <= ( (char *) state->ptrs[state->ptrs_i]->obj + state-> ptrs[state->ptrs_i]->size))) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 391, __FUNCTION__), 0 : 0)) | |||
389 | && ((char *) real_ptr_p + sizeof (void *)((void)(!(real_ptr_p >= state->ptrs[state->ptrs_i]-> obj && ((char *) real_ptr_p + sizeof (void *) <= ( (char *) state->ptrs[state->ptrs_i]->obj + state-> ptrs[state->ptrs_i]->size))) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 391, __FUNCTION__), 0 : 0)) | |||
390 | <= ((char *) state->ptrs[state->ptrs_i]->obj((void)(!(real_ptr_p >= state->ptrs[state->ptrs_i]-> obj && ((char *) real_ptr_p + sizeof (void *) <= ( (char *) state->ptrs[state->ptrs_i]->obj + state-> ptrs[state->ptrs_i]->size))) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 391, __FUNCTION__), 0 : 0)) | |||
391 | + state->ptrs[state->ptrs_i]->size)))((void)(!(real_ptr_p >= state->ptrs[state->ptrs_i]-> obj && ((char *) real_ptr_p + sizeof (void *) <= ( (char *) state->ptrs[state->ptrs_i]->obj + state-> ptrs[state->ptrs_i]->size))) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 391, __FUNCTION__), 0 : 0)); | |||
392 | void *addr | |||
393 | = (void *) ((char *) state->ptrs[state->ptrs_i]->new_addr | |||
394 | + ((char *) real_ptr_p | |||
395 | - (char *) state->ptrs[state->ptrs_i]->obj)); | |||
396 | reloc_addrs_vec.safe_push (addr); | |||
397 | } | |||
398 | ||||
399 | /* Write out, after relocation, the pointers in TAB. */ | |||
400 | static void | |||
401 | write_pch_globals (const struct ggc_root_tab * const *tab, | |||
402 | struct traversal_state *state) | |||
403 | { | |||
404 | const struct ggc_root_tab *const *rt; | |||
405 | const struct ggc_root_tab *rti; | |||
406 | size_t i; | |||
407 | ||||
408 | for (rt = tab; *rt; rt++) | |||
409 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
410 | for (i = 0; i < rti->nelt; i++) | |||
411 | { | |||
412 | void *ptr = *(void **)((char *)rti->base + rti->stride * i); | |||
413 | struct ptr_data *new_ptr; | |||
414 | if (ptr == NULLnullptr || ptr == (void *)1) | |||
415 | { | |||
416 | if (fwrite (&ptr, sizeof (void *), 1, state->f) | |||
417 | != 1) | |||
418 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
419 | } | |||
420 | else | |||
421 | { | |||
422 | new_ptr = (struct ptr_data *) | |||
423 | saving_htab->find_with_hash (ptr, POINTER_HASH (ptr)(hashval_t)((intptr_t)ptr >> 3)); | |||
424 | if (fwrite (&new_ptr->new_addr, sizeof (void *), 1, state->f) | |||
425 | != 1) | |||
426 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
427 | } | |||
428 | } | |||
429 | } | |||
430 | ||||
431 | /* Callback for qsort. */ | |||
432 | ||||
433 | static int | |||
434 | compare_ptr (const void *p1_p, const void *p2_p) | |||
435 | { | |||
436 | void *p1 = *(void *const *)p1_p; | |||
437 | void *p2 = *(void *const *)p2_p; | |||
438 | return (((uintptr_t)p1 > (uintptr_t)p2) | |||
439 | - ((uintptr_t)p1 < (uintptr_t)p2)); | |||
440 | } | |||
441 | ||||
442 | /* Decode one uleb128 from P, return first byte after it, store | |||
443 | decoded value into *VAL. */ | |||
444 | ||||
445 | static unsigned char * | |||
446 | read_uleb128 (unsigned char *p, size_t *val) | |||
447 | { | |||
448 | unsigned int shift = 0; | |||
449 | unsigned char byte; | |||
450 | size_t result; | |||
451 | ||||
452 | result = 0; | |||
453 | do | |||
454 | { | |||
455 | byte = *p++; | |||
456 | result |= ((size_t) byte & 0x7f) << shift; | |||
457 | shift += 7; | |||
458 | } | |||
459 | while (byte & 0x80); | |||
460 | ||||
461 | *val = result; | |||
462 | return p; | |||
463 | } | |||
464 | ||||
465 | /* Store VAL as uleb128 at P, return length in bytes. */ | |||
466 | ||||
467 | static size_t | |||
468 | write_uleb128 (unsigned char *p, size_t val) | |||
469 | { | |||
470 | size_t len = 0; | |||
471 | do | |||
472 | { | |||
473 | unsigned char byte = (val & 0x7f); | |||
474 | val >>= 7; | |||
475 | if (val != 0) | |||
476 | /* More bytes to follow. */ | |||
477 | byte |= 0x80; | |||
478 | ||||
479 | *p++ = byte; | |||
480 | ++len; | |||
481 | } | |||
482 | while (val != 0); | |||
483 | return len; | |||
484 | } | |||
485 | ||||
486 | /* Hold the information we need to mmap the file back in. */ | |||
487 | ||||
488 | struct mmap_info | |||
489 | { | |||
490 | size_t offset; | |||
491 | size_t size; | |||
492 | void *preferred_base; | |||
493 | }; | |||
494 | ||||
495 | /* Write out the state of the compiler to F. */ | |||
496 | ||||
497 | void | |||
498 | gt_pch_save (FILE *f) | |||
499 | { | |||
500 | const struct ggc_root_tab *const *rt; | |||
501 | const struct ggc_root_tab *rti; | |||
502 | size_t i; | |||
503 | struct traversal_state state; | |||
504 | char *this_object = NULLnullptr; | |||
| ||||
505 | size_t this_object_size = 0; | |||
506 | struct mmap_info mmi; | |||
507 | const size_t mmap_offset_alignment = host_hooks.gt_pch_alloc_granularity (); | |||
508 | ||||
509 | gt_pch_save_stringpool (); | |||
510 | ||||
511 | timevar_push (TV_PCH_PTR_REALLOC); | |||
512 | saving_htab = new hash_table<saving_hasher> (50000); | |||
513 | ||||
514 | for (rt = gt_ggc_rtab; *rt; rt++) | |||
515 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
516 | for (i = 0; i < rti->nelt; i++) | |||
517 | (*rti->pchw)(*(void **)((char *)rti->base + rti->stride * i)); | |||
518 | ||||
519 | /* Prepare the objects for writing, determine addresses and such. */ | |||
520 | state.f = f; | |||
521 | state.d = init_ggc_pch (); | |||
522 | state.count = 0; | |||
523 | saving_htab->traverse <traversal_state *, ggc_call_count> (&state); | |||
524 | ||||
525 | mmi.size = ggc_pch_total_size (state.d); | |||
526 | ||||
527 | /* Try to arrange things so that no relocation is necessary, but | |||
528 | don't try very hard. On most platforms, this will always work, | |||
529 | and on the rest it's a lot of work to do better. | |||
530 | (The extra work goes in HOST_HOOKS_GT_PCH_GET_ADDRESS and | |||
531 | HOST_HOOKS_GT_PCH_USE_ADDRESS.) */ | |||
532 | mmi.preferred_base = host_hooks.gt_pch_get_address (mmi.size, fileno (f)fileno_unlocked (f)); | |||
533 | /* If the host cannot supply any suitable address for this, we are stuck. */ | |||
534 | if (mmi.preferred_base == NULLnullptr) | |||
535 | fatal_error (input_location, | |||
536 | "cannot write PCH file: required memory segment unavailable"); | |||
537 | ||||
538 | ggc_pch_this_base (state.d, mmi.preferred_base); | |||
539 | ||||
540 | state.ptrs = XNEWVEC (struct ptr_data *, state.count)((struct ptr_data * *) xmalloc (sizeof (struct ptr_data *) * ( state.count))); | |||
541 | state.ptrs_i = 0; | |||
542 | ||||
543 | saving_htab->traverse <traversal_state *, ggc_call_alloc> (&state); | |||
544 | timevar_pop (TV_PCH_PTR_REALLOC); | |||
545 | ||||
546 | timevar_push (TV_PCH_PTR_SORT); | |||
547 | qsort (state.ptrs, state.count, sizeof (*state.ptrs), compare_ptr_data)gcc_qsort (state.ptrs, state.count, sizeof (*state.ptrs), compare_ptr_data ); | |||
548 | timevar_pop (TV_PCH_PTR_SORT); | |||
549 | ||||
550 | /* Write out all the scalar variables. */ | |||
551 | for (rt = gt_pch_scalar_rtab; *rt; rt++) | |||
552 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
553 | if (fwrite (rti->base, rti->stride, 1, f) != 1) | |||
554 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
555 | ||||
556 | /* Write out all the global pointers, after translation. */ | |||
557 | write_pch_globals (gt_ggc_rtab, &state); | |||
558 | ||||
559 | /* Pad the PCH file so that the mmapped area starts on an allocation | |||
560 | granularity (usually page) boundary. */ | |||
561 | { | |||
562 | long o; | |||
563 | o = ftell (state.f) + sizeof (mmi); | |||
564 | if (o == -1) | |||
565 | fatal_error (input_location, "cannot get position in PCH file: %m"); | |||
566 | mmi.offset = mmap_offset_alignment - o % mmap_offset_alignment; | |||
567 | if (mmi.offset == mmap_offset_alignment) | |||
568 | mmi.offset = 0; | |||
569 | mmi.offset += o; | |||
570 | } | |||
571 | if (fwrite (&mmi, sizeof (mmi), 1, state.f) != 1) | |||
572 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
573 | if (mmi.offset != 0 | |||
574 | && fseek (state.f, mmi.offset, SEEK_SET0) != 0) | |||
575 | fatal_error (input_location, "cannot write padding to PCH file: %m"); | |||
576 | ||||
577 | ggc_pch_prepare_write (state.d, state.f); | |||
578 | ||||
579 | #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS | |||
580 | vec<char> vbits = vNULL; | |||
581 | #endif | |||
582 | ||||
583 | /* Actually write out the objects. */ | |||
584 | for (i = 0; i < state.count; i++) | |||
585 | { | |||
586 | state.ptrs_i = i; | |||
587 | if (this_object_size < state.ptrs[i]->size) | |||
588 | { | |||
589 | this_object_size = state.ptrs[i]->size; | |||
590 | this_object = XRESIZEVAR (char, this_object, this_object_size)((char *) xrealloc ((this_object), (this_object_size))); | |||
591 | } | |||
592 | #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS | |||
593 | /* obj might contain uninitialized bytes, e.g. in the trailing | |||
594 | padding of the object. Avoid warnings by making the memory | |||
595 | temporarily defined and then restoring previous state. */ | |||
596 | int get_vbits = 0; | |||
597 | size_t valid_size = state.ptrs[i]->size; | |||
598 | if (UNLIKELY (RUNNING_ON_VALGRIND)(__builtin_expect ((RUNNING_ON_VALGRIND), 0))) | |||
599 | { | |||
600 | if (vbits.length () < valid_size) | |||
601 | vbits.safe_grow (valid_size, true); | |||
602 | get_vbits = VALGRIND_GET_VBITS (state.ptrs[i]->obj, | |||
603 | vbits.address (), valid_size); | |||
604 | if (get_vbits == 3) | |||
605 | { | |||
606 | /* We assume that first part of obj is addressable, and | |||
607 | the rest is unaddressable. Find out where the boundary is | |||
608 | using binary search. */ | |||
609 | size_t lo = 0, hi = valid_size; | |||
610 | while (hi > lo) | |||
611 | { | |||
612 | size_t mid = (lo + hi) / 2; | |||
613 | get_vbits = VALGRIND_GET_VBITS ((char *) state.ptrs[i]->obj | |||
614 | + mid, vbits.address (), | |||
615 | 1); | |||
616 | if (get_vbits == 3) | |||
617 | hi = mid; | |||
618 | else if (get_vbits == 1) | |||
619 | lo = mid + 1; | |||
620 | else | |||
621 | break; | |||
622 | } | |||
623 | if (get_vbits == 1 || get_vbits == 3) | |||
624 | { | |||
625 | valid_size = lo; | |||
626 | get_vbits = VALGRIND_GET_VBITS (state.ptrs[i]->obj, | |||
627 | vbits.address (), | |||
628 | valid_size); | |||
629 | } | |||
630 | } | |||
631 | if (get_vbits == 1) | |||
632 | VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (state.ptrs[i]->obj, | |||
633 | state.ptrs[i]->size)); | |||
634 | } | |||
635 | #endif | |||
636 | memcpy (this_object, state.ptrs[i]->obj, state.ptrs[i]->size); | |||
| ||||
637 | if (state.ptrs[i]->reorder_fn != NULLnullptr) | |||
638 | state.ptrs[i]->reorder_fn (state.ptrs[i]->obj, | |||
639 | state.ptrs[i]->note_ptr_cookie, | |||
640 | relocate_ptrs, &state); | |||
641 | state.ptrs[i]->note_ptr_fn (state.ptrs[i]->obj, | |||
642 | state.ptrs[i]->note_ptr_cookie, | |||
643 | relocate_ptrs, &state); | |||
644 | ggc_pch_write_object (state.d, state.f, state.ptrs[i]->obj, | |||
645 | state.ptrs[i]->new_addr, state.ptrs[i]->size, | |||
646 | state.ptrs[i]->note_ptr_fn == gt_pch_p_S); | |||
647 | if (state.ptrs[i]->note_ptr_fn != gt_pch_p_S) | |||
648 | memcpy (state.ptrs[i]->obj, this_object, state.ptrs[i]->size); | |||
649 | #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS | |||
650 | if (UNLIKELY (get_vbits == 1)(__builtin_expect ((get_vbits == 1), 0))) | |||
651 | { | |||
652 | (void) VALGRIND_SET_VBITS (state.ptrs[i]->obj, vbits.address (), | |||
653 | valid_size); | |||
654 | if (valid_size != state.ptrs[i]->size) | |||
655 | VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS ((char *) | |||
656 | state.ptrs[i]->obj | |||
657 | + valid_size, | |||
658 | state.ptrs[i]->size | |||
659 | - valid_size)); | |||
660 | } | |||
661 | #endif | |||
662 | } | |||
663 | #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS | |||
664 | vbits.release (); | |||
665 | #endif | |||
666 | ||||
667 | reloc_addrs_vec.qsort (compare_ptr)qsort (compare_ptr); | |||
668 | ||||
669 | size_t reloc_addrs_size = 0; | |||
670 | void *last_addr = NULLnullptr; | |||
671 | unsigned char uleb128_buf[sizeof (size_t) * 2]; | |||
672 | for (void *addr : reloc_addrs_vec) | |||
673 | { | |||
674 | gcc_assert ((uintptr_t) addr >= (uintptr_t) mmi.preferred_base((void)(!((uintptr_t) addr >= (uintptr_t) mmi.preferred_base && ((uintptr_t) addr + sizeof (void *) < (uintptr_t ) mmi.preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 676, __FUNCTION__), 0 : 0)) | |||
675 | && ((uintptr_t) addr + sizeof (void *)((void)(!((uintptr_t) addr >= (uintptr_t) mmi.preferred_base && ((uintptr_t) addr + sizeof (void *) < (uintptr_t ) mmi.preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 676, __FUNCTION__), 0 : 0)) | |||
676 | < (uintptr_t) mmi.preferred_base + mmi.size))((void)(!((uintptr_t) addr >= (uintptr_t) mmi.preferred_base && ((uintptr_t) addr + sizeof (void *) < (uintptr_t ) mmi.preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 676, __FUNCTION__), 0 : 0)); | |||
677 | if (addr == last_addr) | |||
678 | continue; | |||
679 | if (last_addr == NULLnullptr) | |||
680 | last_addr = mmi.preferred_base; | |||
681 | size_t diff = (uintptr_t) addr - (uintptr_t) last_addr; | |||
682 | reloc_addrs_size += write_uleb128 (uleb128_buf, diff); | |||
683 | last_addr = addr; | |||
684 | } | |||
685 | if (fwrite (&reloc_addrs_size, sizeof (reloc_addrs_size), 1, f) != 1) | |||
686 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
687 | last_addr = NULLnullptr; | |||
688 | for (void *addr : reloc_addrs_vec) | |||
689 | { | |||
690 | if (addr == last_addr) | |||
691 | continue; | |||
692 | if (last_addr == NULLnullptr) | |||
693 | last_addr = mmi.preferred_base; | |||
694 | size_t diff = (uintptr_t) addr - (uintptr_t) last_addr; | |||
695 | reloc_addrs_size = write_uleb128 (uleb128_buf, diff); | |||
696 | if (fwrite (uleb128_buf, 1, reloc_addrs_size, f) != reloc_addrs_size) | |||
697 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
698 | last_addr = addr; | |||
699 | } | |||
700 | ||||
701 | ggc_pch_finish (state.d, state.f); | |||
702 | ||||
703 | gt_pch_fixup_stringpool (); | |||
704 | ||||
705 | unsigned num_callbacks = callback_vec.length (); | |||
706 | void (*pch_save) (FILE *) = >_pch_save; | |||
707 | if (fwrite (&pch_save, sizeof (pch_save), 1, f) != 1 | |||
708 | || fwrite (&num_callbacks, sizeof (num_callbacks), 1, f) != 1 | |||
709 | || (num_callbacks | |||
710 | && fwrite (callback_vec.address (), sizeof (void *), num_callbacks, | |||
711 | f) != num_callbacks)) | |||
712 | fatal_error (input_location, "cannot write PCH file: %m"); | |||
713 | ||||
714 | XDELETE (state.ptrs)free ((void*) (state.ptrs)); | |||
715 | XDELETE (this_object)free ((void*) (this_object)); | |||
716 | delete saving_htab; | |||
717 | saving_htab = NULLnullptr; | |||
718 | callback_vec.release (); | |||
719 | reloc_addrs_vec.release (); | |||
720 | } | |||
721 | ||||
722 | /* Read the state of the compiler back in from F. */ | |||
723 | ||||
724 | void | |||
725 | gt_pch_restore (FILE *f) | |||
726 | { | |||
727 | const struct ggc_root_tab *const *rt; | |||
728 | const struct ggc_root_tab *rti; | |||
729 | size_t i; | |||
730 | struct mmap_info mmi; | |||
731 | int result; | |||
732 | ||||
733 | /* We are about to reload the line maps along with the rest of the PCH | |||
734 | data, which means that the (loaded) ones cannot be guaranteed to be | |||
735 | in any valid state for reporting diagnostics that happen during the | |||
736 | load. Save the current table (and use it during the loading process | |||
737 | below). */ | |||
738 | class line_maps *save_line_table = line_table; | |||
739 | ||||
740 | /* Delete any deletable objects. This makes ggc_pch_read much | |||
741 | faster, as it can be sure that no GCable objects remain other | |||
742 | than the ones just read in. */ | |||
743 | for (rt = gt_ggc_deletable_rtab; *rt; rt++) | |||
744 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
745 | memset (rti->base, 0, rti->stride); | |||
746 | ||||
747 | /* Read in all the scalar variables. */ | |||
748 | for (rt = gt_pch_scalar_rtab; *rt; rt++) | |||
749 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
750 | if (fread (rti->base, rti->stride, 1, f) != 1) | |||
751 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
752 | ||||
753 | /* Read in all the global pointers, in 6 easy loops. */ | |||
754 | bool error_reading_pointers = false; | |||
755 | for (rt = gt_ggc_rtab; *rt; rt++) | |||
756 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
757 | for (i = 0; i < rti->nelt; i++) | |||
758 | if (fread ((char *)rti->base + rti->stride * i, | |||
759 | sizeof (void *), 1, f) != 1) | |||
760 | error_reading_pointers = true; | |||
761 | ||||
762 | /* Stash the newly read-in line table pointer - it does not point to | |||
763 | anything meaningful yet, so swap the old one back in. */ | |||
764 | class line_maps *new_line_table = line_table; | |||
765 | line_table = save_line_table; | |||
766 | if (error_reading_pointers) | |||
767 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
768 | ||||
769 | if (fread (&mmi, sizeof (mmi), 1, f) != 1) | |||
770 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
771 | ||||
772 | void *orig_preferred_base = mmi.preferred_base; | |||
773 | result = host_hooks.gt_pch_use_address (mmi.preferred_base, mmi.size, | |||
774 | fileno (f)fileno_unlocked (f), mmi.offset); | |||
775 | ||||
776 | /* We could not mmap or otherwise allocate the required memory at the | |||
777 | address needed. */ | |||
778 | if (result < 0) | |||
779 | { | |||
780 | sorry_at (input_location, "PCH allocation failure"); | |||
781 | /* There is no point in continuing from here, we will only end up | |||
782 | with a crashed (most likely hanging) compiler. */ | |||
783 | exit (-1); | |||
784 | } | |||
785 | ||||
786 | /* (0) We allocated memory, but did not mmap the file, so we need to read | |||
787 | the data in manually. (>0) Otherwise the mmap succeed for the address | |||
788 | we wanted. */ | |||
789 | if (result == 0) | |||
790 | { | |||
791 | if (fseek (f, mmi.offset, SEEK_SET0) != 0 | |||
792 | || fread (mmi.preferred_base, mmi.size, 1, f) != 1) | |||
793 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
794 | } | |||
795 | else if (fseek (f, mmi.offset + mmi.size, SEEK_SET0) != 0) | |||
796 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
797 | ||||
798 | size_t reloc_addrs_size; | |||
799 | if (fread (&reloc_addrs_size, sizeof (reloc_addrs_size), 1, f) != 1) | |||
800 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
801 | ||||
802 | if (orig_preferred_base != mmi.preferred_base) | |||
803 | { | |||
804 | uintptr_t bias | |||
805 | = (uintptr_t) mmi.preferred_base - (uintptr_t) orig_preferred_base; | |||
806 | ||||
807 | /* Adjust all the global pointers by bias. */ | |||
808 | line_table = new_line_table; | |||
809 | for (rt = gt_ggc_rtab; *rt; rt++) | |||
810 | for (rti = *rt; rti->base != NULLnullptr; rti++) | |||
811 | for (i = 0; i < rti->nelt; i++) | |||
812 | { | |||
813 | char *addr = (char *)rti->base + rti->stride * i; | |||
814 | char *p; | |||
815 | memcpy (&p, addr, sizeof (void *)); | |||
816 | if ((uintptr_t) p >= (uintptr_t) orig_preferred_base | |||
817 | && (uintptr_t) p < (uintptr_t) orig_preferred_base + mmi.size) | |||
818 | { | |||
819 | p = (char *) ((uintptr_t) p + bias); | |||
820 | memcpy (addr, &p, sizeof (void *)); | |||
821 | } | |||
822 | } | |||
823 | new_line_table = line_table; | |||
824 | line_table = save_line_table; | |||
825 | ||||
826 | /* And adjust all the pointers in the image by bias too. */ | |||
827 | char *addr = (char *) mmi.preferred_base; | |||
828 | unsigned char uleb128_buf[4096], *uleb128_ptr = uleb128_buf; | |||
829 | while (reloc_addrs_size != 0) | |||
830 | { | |||
831 | size_t this_size | |||
832 | = MIN (reloc_addrs_size,((reloc_addrs_size) < ((size_t) (4096 - (uleb128_ptr - uleb128_buf ))) ? (reloc_addrs_size) : ((size_t) (4096 - (uleb128_ptr - uleb128_buf )))) | |||
833 | (size_t) (4096 - (uleb128_ptr - uleb128_buf)))((reloc_addrs_size) < ((size_t) (4096 - (uleb128_ptr - uleb128_buf ))) ? (reloc_addrs_size) : ((size_t) (4096 - (uleb128_ptr - uleb128_buf )))); | |||
834 | if (fread (uleb128_ptr, 1, this_size, f) != this_size) | |||
835 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
836 | unsigned char *uleb128_end = uleb128_ptr + this_size; | |||
837 | if (this_size != reloc_addrs_size) | |||
838 | uleb128_end -= 2 * sizeof (size_t); | |||
839 | uleb128_ptr = uleb128_buf; | |||
840 | while (uleb128_ptr < uleb128_end) | |||
841 | { | |||
842 | size_t diff; | |||
843 | uleb128_ptr = read_uleb128 (uleb128_ptr, &diff); | |||
844 | addr = (char *) ((uintptr_t) addr + diff); | |||
845 | ||||
846 | char *p; | |||
847 | memcpy (&p, addr, sizeof (void *)); | |||
848 | gcc_assert ((uintptr_t) p >= (uintptr_t) orig_preferred_base((void)(!((uintptr_t) p >= (uintptr_t) orig_preferred_base && ((uintptr_t) p < (uintptr_t) orig_preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 850, __FUNCTION__), 0 : 0)) | |||
849 | && ((uintptr_t) p((void)(!((uintptr_t) p >= (uintptr_t) orig_preferred_base && ((uintptr_t) p < (uintptr_t) orig_preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 850, __FUNCTION__), 0 : 0)) | |||
850 | < (uintptr_t) orig_preferred_base + mmi.size))((void)(!((uintptr_t) p >= (uintptr_t) orig_preferred_base && ((uintptr_t) p < (uintptr_t) orig_preferred_base + mmi.size)) ? fancy_abort ("/buildworker/marxinbox-gcc-clang-static-analyzer/build/gcc/ggc-common.cc" , 850, __FUNCTION__), 0 : 0)); | |||
851 | p = (char *) ((uintptr_t) p + bias); | |||
852 | memcpy (addr, &p, sizeof (void *)); | |||
853 | } | |||
854 | reloc_addrs_size -= this_size; | |||
855 | if (reloc_addrs_size == 0) | |||
856 | break; | |||
857 | this_size = uleb128_end + 2 * sizeof (size_t) - uleb128_ptr; | |||
858 | memcpy (uleb128_buf, uleb128_ptr, this_size); | |||
859 | uleb128_ptr = uleb128_buf + this_size; | |||
860 | } | |||
861 | } | |||
862 | else if (fseek (f, (mmi.offset + mmi.size + sizeof (reloc_addrs_size) | |||
863 | + reloc_addrs_size), SEEK_SET0) != 0) | |||
864 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
865 | ||||
866 | ggc_pch_read (f, mmi.preferred_base); | |||
867 | ||||
868 | void (*pch_save) (FILE *); | |||
869 | unsigned num_callbacks; | |||
870 | if (fread (&pch_save, sizeof (pch_save), 1, f) != 1 | |||
871 | || fread (&num_callbacks, sizeof (num_callbacks), 1, f) != 1) | |||
872 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
873 | if (pch_save != >_pch_save) | |||
874 | { | |||
875 | uintptr_t binbias = (uintptr_t) >_pch_save - (uintptr_t) pch_save; | |||
876 | void **ptrs = XNEWVEC (void *, num_callbacks)((void * *) xmalloc (sizeof (void *) * (num_callbacks))); | |||
877 | unsigned i; | |||
878 | uintptr_t bias | |||
879 | = (uintptr_t) mmi.preferred_base - (uintptr_t) orig_preferred_base; | |||
880 | ||||
881 | if (fread (ptrs, sizeof (void *), num_callbacks, f) != num_callbacks) | |||
882 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
883 | for (i = 0; i < num_callbacks; ++i) | |||
884 | { | |||
885 | void *ptr = (void *) ((uintptr_t) ptrs[i] + bias); | |||
886 | memcpy (&pch_save, ptr, sizeof (pch_save)); | |||
887 | pch_save = (void (*) (FILE *)) ((uintptr_t) pch_save + binbias); | |||
888 | memcpy (ptr, &pch_save, sizeof (pch_save)); | |||
889 | } | |||
890 | XDELETE (ptrs)free ((void*) (ptrs)); | |||
891 | } | |||
892 | else if (fseek (f, num_callbacks * sizeof (void *), SEEK_CUR1) != 0) | |||
893 | fatal_error (input_location, "cannot read PCH file: %m"); | |||
894 | ||||
895 | gt_pch_restore_stringpool (); | |||
896 | ||||
897 | /* Barring corruption of the PCH file, the restored line table should be | |||
898 | complete and usable. */ | |||
899 | line_table = new_line_table; | |||
900 | } | |||
901 | ||||
902 | /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS when mmap is not present. | |||
903 | Select no address whatsoever, and let gt_pch_save choose what it will with | |||
904 | malloc, presumably. */ | |||
905 | ||||
906 | void * | |||
907 | default_gt_pch_get_address (size_t size ATTRIBUTE_UNUSED__attribute__ ((__unused__)), | |||
908 | int fd ATTRIBUTE_UNUSED__attribute__ ((__unused__))) | |||
909 | { | |||
910 | return NULLnullptr; | |||
911 | } | |||
912 | ||||
913 | /* Default version of HOST_HOOKS_GT_PCH_USE_ADDRESS when mmap is not present. | |||
914 | Allocate SIZE bytes with malloc. Return 0 if the address we got is the | |||
915 | same as base, indicating that the memory has been allocated but needs to | |||
916 | be read in from the file. Return -1 if the address differs, to relocation | |||
917 | of the PCH file would be required. */ | |||
918 | ||||
919 | int | |||
920 | default_gt_pch_use_address (void *&base, size_t size, int fd ATTRIBUTE_UNUSED__attribute__ ((__unused__)), | |||
921 | size_t offset ATTRIBUTE_UNUSED__attribute__ ((__unused__))) | |||
922 | { | |||
923 | void *addr = xmalloc (size); | |||
924 | return (addr == base) - 1; | |||
925 | } | |||
926 | ||||
927 | /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS. Return the | |||
928 | alignment required for allocating virtual memory. Usually this is the | |||
929 | same as pagesize. */ | |||
930 | ||||
931 | size_t | |||
932 | default_gt_pch_alloc_granularity (void) | |||
933 | { | |||
934 | return getpagesize (); | |||
935 | } | |||
936 | ||||
937 | #if HAVE_MMAP_FILE1 | |||
938 | /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS when mmap is present. | |||
939 | We temporarily allocate SIZE bytes, and let the kernel place the data | |||
940 | wherever it will. If it worked, that's our spot, if not we're likely | |||
941 | to be in trouble. */ | |||
942 | ||||
943 | void * | |||
944 | mmap_gt_pch_get_address (size_t size, int fd) | |||
945 | { | |||
946 | void *ret; | |||
947 | ||||
948 | ret = mmap (NULLnullptr, size, PROT_READ0x1 | PROT_WRITE0x2, MAP_PRIVATE0x02, fd, 0); | |||
949 | if (ret == (void *) MAP_FAILED((void *) -1)) | |||
950 | ret = NULLnullptr; | |||
951 | else | |||
952 | munmap ((caddr_t) ret, size); | |||
953 | ||||
954 | return ret; | |||
955 | } | |||
956 | ||||
957 | /* Default version of HOST_HOOKS_GT_PCH_USE_ADDRESS when mmap is present. | |||
958 | Map SIZE bytes of FD+OFFSET at BASE. Return 1 if we succeeded at | |||
959 | mapping the data at BASE, -1 if we couldn't. | |||
960 | ||||
961 | This version assumes that the kernel honors the START operand of mmap | |||
962 | even without MAP_FIXED if START through START+SIZE are not currently | |||
963 | mapped with something. */ | |||
964 | ||||
965 | int | |||
966 | mmap_gt_pch_use_address (void *&base, size_t size, int fd, size_t offset) | |||
967 | { | |||
968 | void *addr; | |||
969 | ||||
970 | /* We're called with size == 0 if we're not planning to load a PCH | |||
971 | file at all. This allows the hook to free any static space that | |||
972 | we might have allocated at link time. */ | |||
973 | if (size == 0) | |||
974 | return -1; | |||
975 | ||||
976 | addr = mmap ((caddr_t) base, size, PROT_READ0x1 | PROT_WRITE0x2, MAP_PRIVATE0x02, | |||
977 | fd, offset); | |||
978 | ||||
979 | return addr == base ? 1 : -1; | |||
980 | } | |||
981 | #endif /* HAVE_MMAP_FILE */ | |||
982 | ||||
983 | #if !defined ENABLE_GC_CHECKING1 && !defined ENABLE_GC_ALWAYS_COLLECT | |||
984 | ||||
985 | /* Modify the bound based on rlimits. */ | |||
986 | static double | |||
987 | ggc_rlimit_bound (double limit) | |||
988 | { | |||
989 | #if defined(HAVE_GETRLIMIT1) | |||
990 | struct rlimit rlim; | |||
991 | # if defined (RLIMIT_ASRLIMIT_AS) | |||
992 | /* RLIMIT_AS is what POSIX says is the limit on mmap. Presumably | |||
993 | any OS which has RLIMIT_AS also has a working mmap that GCC will use. */ | |||
994 | if (getrlimit (RLIMIT_ASRLIMIT_AS, &rlim) == 0 | |||
995 | && rlim.rlim_cur != (rlim_t) RLIM_INFINITY((__rlim_t) -1) | |||
996 | && rlim.rlim_cur < limit) | |||
997 | limit = rlim.rlim_cur; | |||
998 | # elif defined (RLIMIT_DATARLIMIT_DATA) | |||
999 | /* ... but some older OSs bound mmap based on RLIMIT_DATA, or we | |||
1000 | might be on an OS that has a broken mmap. (Others don't bound | |||
1001 | mmap at all, apparently.) */ | |||
1002 | if (getrlimit (RLIMIT_DATARLIMIT_DATA, &rlim) == 0 | |||
1003 | && rlim.rlim_cur != (rlim_t) RLIM_INFINITY((__rlim_t) -1) | |||
1004 | && rlim.rlim_cur < limit | |||
1005 | /* Darwin has this horribly bogus default setting of | |||
1006 | RLIMIT_DATA, to 6144Kb. No-one notices because RLIMIT_DATA | |||
1007 | appears to be ignored. Ignore such silliness. If a limit | |||
1008 | this small was actually effective for mmap, GCC wouldn't even | |||
1009 | start up. */ | |||
1010 | && rlim.rlim_cur >= 8 * ONE_M(1024 * 1024)) | |||
1011 | limit = rlim.rlim_cur; | |||
1012 | # endif /* RLIMIT_AS or RLIMIT_DATA */ | |||
1013 | #endif /* HAVE_GETRLIMIT */ | |||
1014 | ||||
1015 | return limit; | |||
1016 | } | |||
1017 | ||||
1018 | /* Heuristic to set a default for GGC_MIN_EXPAND. */ | |||
1019 | static int | |||
1020 | ggc_min_expand_heuristic (void) | |||
1021 | { | |||
1022 | double min_expand = physmem_total (); | |||
1023 | ||||
1024 | /* Adjust for rlimits. */ | |||
1025 | min_expand = ggc_rlimit_bound (min_expand); | |||
1026 | ||||
1027 | /* The heuristic is a percentage equal to 30% + 70%*(RAM/1GB), yielding | |||
1028 | a lower bound of 30% and an upper bound of 100% (when RAM >= 1GB). */ | |||
1029 | min_expand /= ONE_G(1024 * (1024 * 1024)); | |||
1030 | min_expand *= 70; | |||
1031 | min_expand = MIN (min_expand, 70)((min_expand) < (70) ? (min_expand) : (70)); | |||
1032 | min_expand += 30; | |||
1033 | ||||
1034 | return min_expand; | |||
1035 | } | |||
1036 | ||||
1037 | /* Heuristic to set a default for GGC_MIN_HEAPSIZE. */ | |||
1038 | static int | |||
1039 | ggc_min_heapsize_heuristic (void) | |||
1040 | { | |||
1041 | double phys_kbytes = physmem_total (); | |||
1042 | double limit_kbytes = ggc_rlimit_bound (phys_kbytes * 2); | |||
1043 | ||||
1044 | phys_kbytes /= ONE_K1024; /* Convert to Kbytes. */ | |||
1045 | limit_kbytes /= ONE_K1024; | |||
1046 | ||||
1047 | /* The heuristic is RAM/8, with a lower bound of 4M and an upper | |||
1048 | bound of 128M (when RAM >= 1GB). */ | |||
1049 | phys_kbytes /= 8; | |||
1050 | ||||
1051 | #if defined(HAVE_GETRLIMIT1) && defined (RLIMIT_RSS__RLIMIT_RSS) | |||
1052 | /* Try not to overrun the RSS limit while doing garbage collection. | |||
1053 | The RSS limit is only advisory, so no margin is subtracted. */ | |||
1054 | { | |||
1055 | struct rlimit rlim; | |||
1056 | if (getrlimit (RLIMIT_RSS__RLIMIT_RSS, &rlim) == 0 | |||
1057 | && rlim.rlim_cur != (rlim_t) RLIM_INFINITY((__rlim_t) -1)) | |||
1058 | phys_kbytes = MIN (phys_kbytes, rlim.rlim_cur / ONE_K)((phys_kbytes) < (rlim.rlim_cur / 1024) ? (phys_kbytes) : ( rlim.rlim_cur / 1024)); | |||
1059 | } | |||
1060 | # endif | |||
1061 | ||||
1062 | /* Don't blindly run over our data limit; do GC at least when the | |||
1063 | *next* GC would be within 20Mb of the limit or within a quarter of | |||
1064 | the limit, whichever is larger. If GCC does hit the data limit, | |||
1065 | compilation will fail, so this tries to be conservative. */ | |||
1066 | limit_kbytes = MAX (0, limit_kbytes - MAX (limit_kbytes / 4, 20 * ONE_K))((0) > (limit_kbytes - ((limit_kbytes / 4) > (20 * 1024 ) ? (limit_kbytes / 4) : (20 * 1024))) ? (0) : (limit_kbytes - ((limit_kbytes / 4) > (20 * 1024) ? (limit_kbytes / 4) : ( 20 * 1024)))); | |||
1067 | limit_kbytes = (limit_kbytes * 100) / (110 + ggc_min_expand_heuristic ()); | |||
1068 | phys_kbytes = MIN (phys_kbytes, limit_kbytes)((phys_kbytes) < (limit_kbytes) ? (phys_kbytes) : (limit_kbytes )); | |||
1069 | ||||
1070 | phys_kbytes = MAX (phys_kbytes, 4 * ONE_K)((phys_kbytes) > (4 * 1024) ? (phys_kbytes) : (4 * 1024)); | |||
1071 | phys_kbytes = MIN (phys_kbytes, 128 * ONE_K)((phys_kbytes) < (128 * 1024) ? (phys_kbytes) : (128 * 1024 )); | |||
1072 | ||||
1073 | return phys_kbytes; | |||
1074 | } | |||
1075 | #endif | |||
1076 | ||||
1077 | void | |||
1078 | init_ggc_heuristics (void) | |||
1079 | { | |||
1080 | #if !defined ENABLE_GC_CHECKING1 && !defined ENABLE_GC_ALWAYS_COLLECT | |||
1081 | param_ggc_min_expandglobal_options.x_param_ggc_min_expand = ggc_min_expand_heuristic (); | |||
1082 | param_ggc_min_heapsizeglobal_options.x_param_ggc_min_heapsize = ggc_min_heapsize_heuristic (); | |||
1083 | #endif | |||
1084 | } | |||
1085 | ||||
1086 | /* GGC memory usage. */ | |||
1087 | class ggc_usage: public mem_usage | |||
1088 | { | |||
1089 | public: | |||
1090 | /* Default constructor. */ | |||
1091 | ggc_usage (): m_freed (0), m_collected (0), m_overhead (0) {} | |||
1092 | /* Constructor. */ | |||
1093 | ggc_usage (size_t allocated, size_t times, size_t peak, | |||
1094 | size_t freed, size_t collected, size_t overhead) | |||
1095 | : mem_usage (allocated, times, peak), | |||
1096 | m_freed (freed), m_collected (collected), m_overhead (overhead) {} | |||
1097 | ||||
1098 | /* Equality operator. */ | |||
1099 | inline bool | |||
1100 | operator== (const ggc_usage &second) const | |||
1101 | { | |||
1102 | return (get_balance () == second.get_balance () | |||
1103 | && m_peak == second.m_peak | |||
1104 | && m_times == second.m_times); | |||
1105 | } | |||
1106 | ||||
1107 | /* Comparison operator. */ | |||
1108 | inline bool | |||
1109 | operator< (const ggc_usage &second) const | |||
1110 | { | |||
1111 | if (*this == second) | |||
1112 | return false; | |||
1113 | ||||
1114 | return (get_balance () == second.get_balance () ? | |||
1115 | (m_peak == second.m_peak ? m_times < second.m_times | |||
1116 | : m_peak < second.m_peak) | |||
1117 | : get_balance () < second.get_balance ()); | |||
1118 | } | |||
1119 | ||||
1120 | /* Register overhead of ALLOCATED and OVERHEAD bytes. */ | |||
1121 | inline void | |||
1122 | register_overhead (size_t allocated, size_t overhead) | |||
1123 | { | |||
1124 | m_allocated += allocated; | |||
1125 | m_overhead += overhead; | |||
1126 | m_times++; | |||
1127 | } | |||
1128 | ||||
1129 | /* Release overhead of SIZE bytes. */ | |||
1130 | inline void | |||
1131 | release_overhead (size_t size) | |||
1132 | { | |||
1133 | m_freed += size; | |||
1134 | } | |||
1135 | ||||
1136 | /* Sum the usage with SECOND usage. */ | |||
1137 | ggc_usage | |||
1138 | operator+ (const ggc_usage &second) | |||
1139 | { | |||
1140 | return ggc_usage (m_allocated + second.m_allocated, | |||
1141 | m_times + second.m_times, | |||
1142 | m_peak + second.m_peak, | |||
1143 | m_freed + second.m_freed, | |||
1144 | m_collected + second.m_collected, | |||
1145 | m_overhead + second.m_overhead); | |||
1146 | } | |||
1147 | ||||
1148 | /* Dump usage with PREFIX, where TOTAL is sum of all rows. */ | |||
1149 | inline void | |||
1150 | dump (const char *prefix, ggc_usage &total) const | |||
1151 | { | |||
1152 | size_t balance = get_balance (); | |||
1153 | fprintf (stderrstderr, | |||
1154 | "%-48s " PRsa (9)"%" "9" "l" "u" "%c" ":%5.1f%%" PRsa (9)"%" "9" "l" "u" "%c" ":%5.1f%%" | |||
1155 | PRsa (9)"%" "9" "l" "u" "%c" ":%5.1f%%" PRsa (9)"%" "9" "l" "u" "%c" ":%5.1f%%" PRsa (9)"%" "9" "l" "u" "%c" "\n", | |||
1156 | prefix, | |||
1157 | SIZE_AMOUNT (balance)(uint64_t)(((balance) < 10 * 1024 ? (balance) : ((balance) < 10 * (1024 * 1024) ? (balance) / 1024 : (balance) / (1024 * 1024)))), ((balance) < 10 * 1024 ? ' ' : ((balance) < 10 * (1024 * 1024) ? 'k' : 'M')), get_percent (balance, total.get_balance ()), | |||
1158 | SIZE_AMOUNT (m_collected)(uint64_t)(((m_collected) < 10 * 1024 ? (m_collected) : (( m_collected) < 10 * (1024 * 1024) ? (m_collected) / 1024 : (m_collected) / (1024 * 1024)))), ((m_collected) < 10 * 1024 ? ' ' : ((m_collected) < 10 * (1024 * 1024) ? 'k' : 'M')), | |||
1159 | get_percent (m_collected, total.m_collected), | |||
1160 | SIZE_AMOUNT (m_freed)(uint64_t)(((m_freed) < 10 * 1024 ? (m_freed) : ((m_freed) < 10 * (1024 * 1024) ? (m_freed) / 1024 : (m_freed) / (1024 * 1024)))), ((m_freed) < 10 * 1024 ? ' ' : ((m_freed) < 10 * (1024 * 1024) ? 'k' : 'M')), get_percent (m_freed, total.m_freed), | |||
1161 | SIZE_AMOUNT (m_overhead)(uint64_t)(((m_overhead) < 10 * 1024 ? (m_overhead) : ((m_overhead ) < 10 * (1024 * 1024) ? (m_overhead) / 1024 : (m_overhead ) / (1024 * 1024)))), ((m_overhead) < 10 * 1024 ? ' ' : (( m_overhead) < 10 * (1024 * 1024) ? 'k' : 'M')), | |||
1162 | get_percent (m_overhead, total.m_overhead), | |||
1163 | SIZE_AMOUNT (m_times)(uint64_t)(((m_times) < 10 * 1024 ? (m_times) : ((m_times) < 10 * (1024 * 1024) ? (m_times) / 1024 : (m_times) / (1024 * 1024)))), ((m_times) < 10 * 1024 ? ' ' : ((m_times) < 10 * (1024 * 1024) ? 'k' : 'M'))); | |||
1164 | } | |||
1165 | ||||
1166 | /* Dump usage coupled to LOC location, where TOTAL is sum of all rows. */ | |||
1167 | inline void | |||
1168 | dump (mem_location *loc, ggc_usage &total) const | |||
1169 | { | |||
1170 | char *location_string = loc->to_string (); | |||
1171 | ||||
1172 | dump (location_string, total); | |||
1173 | ||||
1174 | free (location_string); | |||
1175 | } | |||
1176 | ||||
1177 | /* Dump footer. */ | |||
1178 | inline void | |||
1179 | dump_footer () | |||
1180 | { | |||
1181 | dump ("Total", *this); | |||
1182 | } | |||
1183 | ||||
1184 | /* Get balance which is GGC allocation leak. */ | |||
1185 | inline size_t | |||
1186 | get_balance () const | |||
1187 | { | |||
1188 | return m_allocated + m_overhead - m_collected - m_freed; | |||
1189 | } | |||
1190 | ||||
1191 | typedef std::pair<mem_location *, ggc_usage *> mem_pair_t; | |||
1192 | ||||
1193 | /* Compare wrapper used by qsort method. */ | |||
1194 | static int | |||
1195 | compare (const void *first, const void *second) | |||
1196 | { | |||
1197 | const mem_pair_t mem1 = *(const mem_pair_t *) first; | |||
1198 | const mem_pair_t mem2 = *(const mem_pair_t *) second; | |||
1199 | ||||
1200 | size_t balance1 = mem1.second->get_balance (); | |||
1201 | size_t balance2 = mem2.second->get_balance (); | |||
1202 | ||||
1203 | return balance1 == balance2 ? 0 : (balance1 < balance2 ? 1 : -1); | |||
1204 | } | |||
1205 | ||||
1206 | /* Dump header with NAME. */ | |||
1207 | static inline void | |||
1208 | dump_header (const char *name) | |||
1209 | { | |||
1210 | fprintf (stderrstderr, "%-48s %11s%17s%17s%16s%17s\n", name, "Leak", "Garbage", | |||
1211 | "Freed", "Overhead", "Times"); | |||
1212 | } | |||
1213 | ||||
1214 | /* Freed memory in bytes. */ | |||
1215 | size_t m_freed; | |||
1216 | /* Collected memory in bytes. */ | |||
1217 | size_t m_collected; | |||
1218 | /* Overhead memory in bytes. */ | |||
1219 | size_t m_overhead; | |||
1220 | }; | |||
1221 | ||||
1222 | /* GCC memory description. */ | |||
1223 | static mem_alloc_description<ggc_usage> ggc_mem_desc; | |||
1224 | ||||
1225 | /* Dump per-site memory statistics. */ | |||
1226 | ||||
1227 | void | |||
1228 | dump_ggc_loc_statistics () | |||
1229 | { | |||
1230 | if (! GATHER_STATISTICS0) | |||
1231 | return; | |||
1232 | ||||
1233 | ggc_collect (GGC_COLLECT_FORCE); | |||
1234 | ||||
1235 | ggc_mem_desc.dump (GGC_ORIGIN); | |||
1236 | } | |||
1237 | ||||
1238 | /* Record ALLOCATED and OVERHEAD bytes to descriptor NAME:LINE (FUNCTION). */ | |||
1239 | void | |||
1240 | ggc_record_overhead (size_t allocated, size_t overhead, void *ptr MEM_STAT_DECL) | |||
1241 | { | |||
1242 | ggc_usage *usage = ggc_mem_desc.register_descriptor (ptr, GGC_ORIGIN, false | |||
1243 | FINAL_PASS_MEM_STAT, 0,0,0); | |||
1244 | ||||
1245 | ggc_mem_desc.register_object_overhead (usage, allocated + overhead, ptr); | |||
1246 | usage->register_overhead (allocated, overhead); | |||
1247 | } | |||
1248 | ||||
1249 | /* Notice that the pointer has been freed. */ | |||
1250 | void | |||
1251 | ggc_free_overhead (void *ptr) | |||
1252 | { | |||
1253 | ggc_mem_desc.release_object_overhead (ptr); | |||
1254 | } | |||
1255 | ||||
1256 | /* After live values has been marked, walk all recorded pointers and see if | |||
1257 | they are still live. */ | |||
1258 | void | |||
1259 | ggc_prune_overhead_list (void) | |||
1260 | { | |||
1261 | typedef hash_map<const void *, std::pair<ggc_usage *, size_t > > map_t; | |||
1262 | ||||
1263 | map_t::iterator it = ggc_mem_desc.m_reverse_object_map->begin (); | |||
1264 | ||||
1265 | for (; it != ggc_mem_desc.m_reverse_object_map->end (); ++it) | |||
1266 | if (!ggc_marked_p ((*it).first)) | |||
1267 | { | |||
1268 | (*it).second.first->m_collected += (*it).second.second; | |||
1269 | ggc_mem_desc.m_reverse_object_map->remove ((*it).first); | |||
1270 | } | |||
1271 | } | |||
1272 | ||||
1273 | /* Print memory used by heap if this info is available. */ | |||
1274 | ||||
1275 | void | |||
1276 | report_heap_memory_use () | |||
1277 | { | |||
1278 | #if defined(HAVE_MALLINFO1) || defined(HAVE_MALLINFO21) | |||
1279 | #ifdef HAVE_MALLINFO21 | |||
1280 | #define MALLINFO_FNmallinfo2 mallinfo2 | |||
1281 | #else | |||
1282 | #define MALLINFO_FNmallinfo2 mallinfo | |||
1283 | #endif | |||
1284 | if (!quiet_flagglobal_options.x_quiet_flag) | |||
1285 | fprintf (stderrstderr, " {heap " PRsa (0)"%" "0" "l" "u" "%c" "}", | |||
1286 | SIZE_AMOUNT (MALLINFO_FN ().arena)(uint64_t)(((mallinfo2 ().arena) < 10 * 1024 ? (mallinfo2 ( ).arena) : ((mallinfo2 ().arena) < 10 * (1024 * 1024) ? (mallinfo2 ().arena) / 1024 : (mallinfo2 ().arena) / (1024 * 1024)))), ( (mallinfo2 ().arena) < 10 * 1024 ? ' ' : ((mallinfo2 ().arena ) < 10 * (1024 * 1024) ? 'k' : 'M'))); | |||
1287 | #endif | |||
1288 | } |