| /* Simple garbage collection for the GNU compiler. |
| Copyright (C) 1999-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* Generic garbage collection (GC) functions and data, not specific to |
| any particular GC implementation. */ |
| |
| #include "config.h" |
| #define INCLUDE_MALLOC_H |
| #include "system.h" |
| #include "coretypes.h" |
| #include "timevar.h" |
| #include "diagnostic-core.h" |
| #include "ggc-internal.h" |
| #include "hosthooks.h" |
| #include "plugin.h" |
| #include "options.h" |
| |
| /* When true, protect the contents of the identifier hash table. */ |
| bool ggc_protect_identifiers = true; |
| |
| /* Statistics about the allocation. */ |
| static ggc_statistics *ggc_stats; |
| |
| struct traversal_state; |
| |
| static int compare_ptr_data (const void *, const void *); |
| static void relocate_ptrs (void *, void *, void *); |
| static void write_pch_globals (const struct ggc_root_tab * const *tab, |
| struct traversal_state *state); |
| |
| /* Maintain global roots that are preserved during GC. */ |
| |
| /* This extra vector of dynamically registered root_tab-s is used by |
| ggc_mark_roots and gives the ability to dynamically add new GGC root |
| tables, for instance from some plugins; this vector is on the heap |
| since it is used by GGC internally. */ |
| typedef const struct ggc_root_tab *const_ggc_root_tab_t; |
| static vec<const_ggc_root_tab_t> extra_root_vec; |
| |
| /* Dynamically register a new GGC root table RT. This is useful for |
| plugins. */ |
| |
| void |
| ggc_register_root_tab (const struct ggc_root_tab* rt) |
| { |
| if (rt) |
| extra_root_vec.safe_push (rt); |
| } |
| |
| /* Mark all the roots in the table RT. */ |
| |
| static void |
| ggc_mark_root_tab (const_ggc_root_tab_t rt) |
| { |
| size_t i; |
| |
| for ( ; rt->base != NULL; rt++) |
| for (i = 0; i < rt->nelt; i++) |
| (*rt->cb) (*(void **) ((char *)rt->base + rt->stride * i)); |
| } |
| |
| /* Iterate through all registered roots and mark each element. */ |
| |
| void |
| ggc_mark_roots (void) |
| { |
| const struct ggc_root_tab *const *rt; |
| const_ggc_root_tab_t rtp, rti; |
| size_t i; |
| |
| for (rt = gt_ggc_deletable_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| memset (rti->base, 0, rti->stride); |
| |
| for (rt = gt_ggc_rtab; *rt; rt++) |
| ggc_mark_root_tab (*rt); |
| |
| FOR_EACH_VEC_ELT (extra_root_vec, i, rtp) |
| ggc_mark_root_tab (rtp); |
| |
| if (ggc_protect_identifiers) |
| ggc_mark_stringpool (); |
| |
| gt_clear_caches (); |
| |
| if (! ggc_protect_identifiers) |
| ggc_purge_stringpool (); |
| |
| /* Some plugins may call ggc_set_mark from here. */ |
| invoke_plugin_callbacks (PLUGIN_GGC_MARKING, NULL); |
| } |
| |
| /* Allocate a block of memory, then clear it. */ |
| void * |
| ggc_internal_cleared_alloc (size_t size, void (*f)(void *), size_t s, size_t n |
| MEM_STAT_DECL) |
| { |
| void *buf = ggc_internal_alloc (size, f, s, n PASS_MEM_STAT); |
| memset (buf, 0, size); |
| return buf; |
| } |
| |
| /* Resize a block of memory, possibly re-allocating it. */ |
| void * |
| ggc_realloc (void *x, size_t size MEM_STAT_DECL) |
| { |
| void *r; |
| size_t old_size; |
| |
| if (x == NULL) |
| return ggc_internal_alloc (size PASS_MEM_STAT); |
| |
| old_size = ggc_get_size (x); |
| |
| if (size <= old_size) |
| { |
| /* Mark the unwanted memory as unaccessible. We also need to make |
| the "new" size accessible, since ggc_get_size returns the size of |
| the pool, not the size of the individually allocated object, the |
| size which was previously made accessible. Unfortunately, we |
| don't know that previously allocated size. Without that |
| knowledge we have to lose some initialization-tracking for the |
| old parts of the object. An alternative is to mark the whole |
| old_size as reachable, but that would lose tracking of writes |
| after the end of the object (by small offsets). Discard the |
| handle to avoid handle leak. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS ((char *) x + size, |
| old_size - size)); |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (x, size)); |
| return x; |
| } |
| |
| r = ggc_internal_alloc (size PASS_MEM_STAT); |
| |
| /* Since ggc_get_size returns the size of the pool, not the size of the |
| individually allocated object, we'd access parts of the old object |
| that were marked invalid with the memcpy below. We lose a bit of the |
| initialization-tracking since some of it may be uninitialized. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (x, old_size)); |
| |
| memcpy (r, x, old_size); |
| |
| /* The old object is not supposed to be used anymore. */ |
| ggc_free (x); |
| |
| return r; |
| } |
| |
| void * |
| ggc_cleared_alloc_htab_ignore_args (size_t c ATTRIBUTE_UNUSED, |
| size_t n ATTRIBUTE_UNUSED) |
| { |
| gcc_assert (c * n == sizeof (struct htab)); |
| return ggc_cleared_alloc<htab> (); |
| } |
| |
| /* TODO: once we actually use type information in GGC, create a new tag |
| gt_gcc_ptr_array and use it for pointer arrays. */ |
| void * |
| ggc_cleared_alloc_ptr_array_two_args (size_t c, size_t n) |
| { |
| gcc_assert (sizeof (void **) == n); |
| return ggc_cleared_vec_alloc<void **> (c); |
| } |
| |
| /* These are for splay_tree_new_ggc. */ |
| void * |
| ggc_splay_alloc (int sz, void *nl) |
| { |
| gcc_assert (!nl); |
| return ggc_internal_alloc (sz); |
| } |
| |
| void |
| ggc_splay_dont_free (void * x ATTRIBUTE_UNUSED, void *nl) |
| { |
| gcc_assert (!nl); |
| } |
| |
| void |
| ggc_print_common_statistics (FILE *stream ATTRIBUTE_UNUSED, |
| ggc_statistics *stats) |
| { |
| /* Set the pointer so that during collection we will actually gather |
| the statistics. */ |
| ggc_stats = stats; |
| |
| /* Then do one collection to fill in the statistics. */ |
| ggc_collect (); |
| |
| /* At present, we don't really gather any interesting statistics. */ |
| |
| /* Don't gather statistics any more. */ |
| ggc_stats = NULL; |
| } |
| |
| /* Functions for saving and restoring GCable memory to disk. */ |
| |
| struct ptr_data |
| { |
| void *obj; |
| void *note_ptr_cookie; |
| gt_note_pointers note_ptr_fn; |
| gt_handle_reorder reorder_fn; |
| size_t size; |
| void *new_addr; |
| }; |
| |
| #define POINTER_HASH(x) (hashval_t)((intptr_t)x >> 3) |
| |
| /* Helper for hashing saving_htab. */ |
| |
| struct saving_hasher : free_ptr_hash <ptr_data> |
| { |
| typedef void *compare_type; |
| static inline hashval_t hash (const ptr_data *); |
| static inline bool equal (const ptr_data *, const void *); |
| }; |
| |
| inline hashval_t |
| saving_hasher::hash (const ptr_data *p) |
| { |
| return POINTER_HASH (p->obj); |
| } |
| |
| inline bool |
| saving_hasher::equal (const ptr_data *p1, const void *p2) |
| { |
| return p1->obj == p2; |
| } |
| |
| static hash_table<saving_hasher> *saving_htab; |
| static vec<void *> callback_vec; |
| static vec<void *> reloc_addrs_vec; |
| |
| /* Register an object in the hash table. */ |
| |
| int |
| gt_pch_note_object (void *obj, void *note_ptr_cookie, |
| gt_note_pointers note_ptr_fn, |
| size_t length_override) |
| { |
| struct ptr_data **slot; |
| |
| if (obj == NULL || obj == (void *) 1) |
| return 0; |
| |
| slot = (struct ptr_data **) |
| saving_htab->find_slot_with_hash (obj, POINTER_HASH (obj), INSERT); |
| if (*slot != NULL) |
| { |
| gcc_assert ((*slot)->note_ptr_fn == note_ptr_fn |
| && (*slot)->note_ptr_cookie == note_ptr_cookie); |
| return 0; |
| } |
| |
| *slot = XCNEW (struct ptr_data); |
| (*slot)->obj = obj; |
| (*slot)->note_ptr_fn = note_ptr_fn; |
| (*slot)->note_ptr_cookie = note_ptr_cookie; |
| if (length_override != (size_t)-1) |
| (*slot)->size = length_override; |
| else if (note_ptr_fn == gt_pch_p_S) |
| (*slot)->size = strlen ((const char *)obj) + 1; |
| else |
| (*slot)->size = ggc_get_size (obj); |
| return 1; |
| } |
| |
| /* Register address of a callback pointer. */ |
| void |
| gt_pch_note_callback (void *obj, void *base) |
| { |
| void *ptr; |
| memcpy (&ptr, obj, sizeof (void *)); |
| if (ptr != NULL) |
| { |
| struct ptr_data *data |
| = (struct ptr_data *) |
| saving_htab->find_with_hash (base, POINTER_HASH (base)); |
| gcc_assert (data); |
| callback_vec.safe_push ((char *) data->new_addr |
| + ((char *) obj - (char *) base)); |
| } |
| } |
| |
| /* Register an object in the hash table. */ |
| |
| void |
| gt_pch_note_reorder (void *obj, void *note_ptr_cookie, |
| gt_handle_reorder reorder_fn) |
| { |
| struct ptr_data *data; |
| |
| if (obj == NULL || obj == (void *) 1) |
| return; |
| |
| data = (struct ptr_data *) |
| saving_htab->find_with_hash (obj, POINTER_HASH (obj)); |
| gcc_assert (data && data->note_ptr_cookie == note_ptr_cookie); |
| |
| data->reorder_fn = reorder_fn; |
| } |
| |
| /* Handy state for the traversal functions. */ |
| |
| struct traversal_state |
| { |
| FILE *f; |
| struct ggc_pch_data *d; |
| size_t count; |
| struct ptr_data **ptrs; |
| size_t ptrs_i; |
| }; |
| |
| /* Callbacks for htab_traverse. */ |
| |
| int |
| ggc_call_count (ptr_data **slot, traversal_state *state) |
| { |
| struct ptr_data *d = *slot; |
| |
| ggc_pch_count_object (state->d, d->obj, d->size, |
| d->note_ptr_fn == gt_pch_p_S); |
| state->count++; |
| return 1; |
| } |
| |
| int |
| ggc_call_alloc (ptr_data **slot, traversal_state *state) |
| { |
| struct ptr_data *d = *slot; |
| |
| d->new_addr = ggc_pch_alloc_object (state->d, d->obj, d->size, |
| d->note_ptr_fn == gt_pch_p_S); |
| state->ptrs[state->ptrs_i++] = d; |
| return 1; |
| } |
| |
| /* Callback for qsort. */ |
| |
| static int |
| compare_ptr_data (const void *p1_p, const void *p2_p) |
| { |
| const struct ptr_data *const p1 = *(const struct ptr_data *const *)p1_p; |
| const struct ptr_data *const p2 = *(const struct ptr_data *const *)p2_p; |
| return (((size_t)p1->new_addr > (size_t)p2->new_addr) |
| - ((size_t)p1->new_addr < (size_t)p2->new_addr)); |
| } |
| |
| /* Callbacks for note_ptr_fn. */ |
| |
| static void |
| relocate_ptrs (void *ptr_p, void *real_ptr_p, void *state_p) |
| { |
| void **ptr = (void **)ptr_p; |
| struct traversal_state *state |
| = (struct traversal_state *)state_p; |
| struct ptr_data *result; |
| |
| if (*ptr == NULL || *ptr == (void *)1) |
| return; |
| |
| result = (struct ptr_data *) |
| saving_htab->find_with_hash (*ptr, POINTER_HASH (*ptr)); |
| gcc_assert (result); |
| *ptr = result->new_addr; |
| if (ptr_p == real_ptr_p) |
| return; |
| if (real_ptr_p == NULL) |
| real_ptr_p = ptr_p; |
| gcc_assert (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))); |
| void *addr |
| = (void *) ((char *) state->ptrs[state->ptrs_i]->new_addr |
| + ((char *) real_ptr_p |
| - (char *) state->ptrs[state->ptrs_i]->obj)); |
| reloc_addrs_vec.safe_push (addr); |
| } |
| |
| /* Write out, after relocation, the pointers in TAB. */ |
| static void |
| write_pch_globals (const struct ggc_root_tab * const *tab, |
| struct traversal_state *state) |
| { |
| const struct ggc_root_tab *const *rt; |
| const struct ggc_root_tab *rti; |
| size_t i; |
| |
| for (rt = tab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| for (i = 0; i < rti->nelt; i++) |
| { |
| void *ptr = *(void **)((char *)rti->base + rti->stride * i); |
| struct ptr_data *new_ptr; |
| if (ptr == NULL || ptr == (void *)1) |
| { |
| if (fwrite (&ptr, sizeof (void *), 1, state->f) |
| != 1) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| } |
| else |
| { |
| new_ptr = (struct ptr_data *) |
| saving_htab->find_with_hash (ptr, POINTER_HASH (ptr)); |
| if (fwrite (&new_ptr->new_addr, sizeof (void *), 1, state->f) |
| != 1) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| } |
| } |
| } |
| |
| /* Callback for qsort. */ |
| |
| static int |
| compare_ptr (const void *p1_p, const void *p2_p) |
| { |
| void *p1 = *(void *const *)p1_p; |
| void *p2 = *(void *const *)p2_p; |
| return (((uintptr_t)p1 > (uintptr_t)p2) |
| - ((uintptr_t)p1 < (uintptr_t)p2)); |
| } |
| |
| /* Decode one uleb128 from P, return first byte after it, store |
| decoded value into *VAL. */ |
| |
| static unsigned char * |
| read_uleb128 (unsigned char *p, size_t *val) |
| { |
| unsigned int shift = 0; |
| unsigned char byte; |
| size_t result; |
| |
| result = 0; |
| do |
| { |
| byte = *p++; |
| result |= ((size_t) byte & 0x7f) << shift; |
| shift += 7; |
| } |
| while (byte & 0x80); |
| |
| *val = result; |
| return p; |
| } |
| |
| /* Store VAL as uleb128 at P, return length in bytes. */ |
| |
| static size_t |
| write_uleb128 (unsigned char *p, size_t val) |
| { |
| size_t len = 0; |
| do |
| { |
| unsigned char byte = (val & 0x7f); |
| val >>= 7; |
| if (val != 0) |
| /* More bytes to follow. */ |
| byte |= 0x80; |
| |
| *p++ = byte; |
| ++len; |
| } |
| while (val != 0); |
| return len; |
| } |
| |
| /* Hold the information we need to mmap the file back in. */ |
| |
| struct mmap_info |
| { |
| size_t offset; |
| size_t size; |
| void *preferred_base; |
| }; |
| |
| /* Write out the state of the compiler to F. */ |
| |
| void |
| gt_pch_save (FILE *f) |
| { |
| const struct ggc_root_tab *const *rt; |
| const struct ggc_root_tab *rti; |
| size_t i; |
| struct traversal_state state; |
| char *this_object = NULL; |
| size_t this_object_size = 0; |
| struct mmap_info mmi; |
| const size_t mmap_offset_alignment = host_hooks.gt_pch_alloc_granularity (); |
| |
| gt_pch_save_stringpool (); |
| |
| timevar_push (TV_PCH_PTR_REALLOC); |
| saving_htab = new hash_table<saving_hasher> (50000); |
| |
| for (rt = gt_ggc_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| for (i = 0; i < rti->nelt; i++) |
| (*rti->pchw)(*(void **)((char *)rti->base + rti->stride * i)); |
| |
| /* Prepare the objects for writing, determine addresses and such. */ |
| state.f = f; |
| state.d = init_ggc_pch (); |
| state.count = 0; |
| saving_htab->traverse <traversal_state *, ggc_call_count> (&state); |
| |
| mmi.size = ggc_pch_total_size (state.d); |
| |
| /* Try to arrange things so that no relocation is necessary, but |
| don't try very hard. On most platforms, this will always work, |
| and on the rest it's a lot of work to do better. |
| (The extra work goes in HOST_HOOKS_GT_PCH_GET_ADDRESS and |
| HOST_HOOKS_GT_PCH_USE_ADDRESS.) */ |
| mmi.preferred_base = host_hooks.gt_pch_get_address (mmi.size, fileno (f)); |
| /* If the host cannot supply any suitable address for this, we are stuck. */ |
| if (mmi.preferred_base == NULL) |
| fatal_error (input_location, |
| "cannot write PCH file: required memory segment unavailable"); |
| |
| ggc_pch_this_base (state.d, mmi.preferred_base); |
| |
| state.ptrs = XNEWVEC (struct ptr_data *, state.count); |
| state.ptrs_i = 0; |
| |
| saving_htab->traverse <traversal_state *, ggc_call_alloc> (&state); |
| timevar_pop (TV_PCH_PTR_REALLOC); |
| |
| timevar_push (TV_PCH_PTR_SORT); |
| qsort (state.ptrs, state.count, sizeof (*state.ptrs), compare_ptr_data); |
| timevar_pop (TV_PCH_PTR_SORT); |
| |
| /* Write out all the scalar variables. */ |
| for (rt = gt_pch_scalar_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| if (fwrite (rti->base, rti->stride, 1, f) != 1) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| |
| /* Write out all the global pointers, after translation. */ |
| write_pch_globals (gt_ggc_rtab, &state); |
| |
| /* Pad the PCH file so that the mmapped area starts on an allocation |
| granularity (usually page) boundary. */ |
| { |
| long o; |
| o = ftell (state.f) + sizeof (mmi); |
| if (o == -1) |
| fatal_error (input_location, "cannot get position in PCH file: %m"); |
| mmi.offset = mmap_offset_alignment - o % mmap_offset_alignment; |
| if (mmi.offset == mmap_offset_alignment) |
| mmi.offset = 0; |
| mmi.offset += o; |
| } |
| if (fwrite (&mmi, sizeof (mmi), 1, state.f) != 1) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| if (mmi.offset != 0 |
| && fseek (state.f, mmi.offset, SEEK_SET) != 0) |
| fatal_error (input_location, "cannot write padding to PCH file: %m"); |
| |
| ggc_pch_prepare_write (state.d, state.f); |
| |
| #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS |
| vec<char> vbits = vNULL; |
| #endif |
| |
| /* Actually write out the objects. */ |
| for (i = 0; i < state.count; i++) |
| { |
| state.ptrs_i = i; |
| if (this_object_size < state.ptrs[i]->size) |
| { |
| this_object_size = state.ptrs[i]->size; |
| this_object = XRESIZEVAR (char, this_object, this_object_size); |
| } |
| #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS |
| /* obj might contain uninitialized bytes, e.g. in the trailing |
| padding of the object. Avoid warnings by making the memory |
| temporarily defined and then restoring previous state. */ |
| int get_vbits = 0; |
| size_t valid_size = state.ptrs[i]->size; |
| if (UNLIKELY (RUNNING_ON_VALGRIND)) |
| { |
| if (vbits.length () < valid_size) |
| vbits.safe_grow (valid_size, true); |
| get_vbits = VALGRIND_GET_VBITS (state.ptrs[i]->obj, |
| vbits.address (), valid_size); |
| if (get_vbits == 3) |
| { |
| /* We assume that first part of obj is addressable, and |
| the rest is unaddressable. Find out where the boundary is |
| using binary search. */ |
| size_t lo = 0, hi = valid_size; |
| while (hi > lo) |
| { |
| size_t mid = (lo + hi) / 2; |
| get_vbits = VALGRIND_GET_VBITS ((char *) state.ptrs[i]->obj |
| + mid, vbits.address (), |
| 1); |
| if (get_vbits == 3) |
| hi = mid; |
| else if (get_vbits == 1) |
| lo = mid + 1; |
| else |
| break; |
| } |
| if (get_vbits == 1 || get_vbits == 3) |
| { |
| valid_size = lo; |
| get_vbits = VALGRIND_GET_VBITS (state.ptrs[i]->obj, |
| vbits.address (), |
| valid_size); |
| } |
| } |
| if (get_vbits == 1) |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_DEFINED (state.ptrs[i]->obj, |
| state.ptrs[i]->size)); |
| } |
| #endif |
| memcpy (this_object, state.ptrs[i]->obj, state.ptrs[i]->size); |
| if (state.ptrs[i]->reorder_fn != NULL) |
| state.ptrs[i]->reorder_fn (state.ptrs[i]->obj, |
| state.ptrs[i]->note_ptr_cookie, |
| relocate_ptrs, &state); |
| state.ptrs[i]->note_ptr_fn (state.ptrs[i]->obj, |
| state.ptrs[i]->note_ptr_cookie, |
| relocate_ptrs, &state); |
| ggc_pch_write_object (state.d, state.f, state.ptrs[i]->obj, |
| state.ptrs[i]->new_addr, state.ptrs[i]->size, |
| state.ptrs[i]->note_ptr_fn == gt_pch_p_S); |
| if (state.ptrs[i]->note_ptr_fn != gt_pch_p_S) |
| memcpy (state.ptrs[i]->obj, this_object, state.ptrs[i]->size); |
| #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS |
| if (UNLIKELY (get_vbits == 1)) |
| { |
| (void) VALGRIND_SET_VBITS (state.ptrs[i]->obj, vbits.address (), |
| valid_size); |
| if (valid_size != state.ptrs[i]->size) |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS ((char *) |
| state.ptrs[i]->obj |
| + valid_size, |
| state.ptrs[i]->size |
| - valid_size)); |
| } |
| #endif |
| } |
| #if defined ENABLE_VALGRIND_ANNOTATIONS && defined VALGRIND_GET_VBITS |
| vbits.release (); |
| #endif |
| |
| reloc_addrs_vec.qsort (compare_ptr); |
| |
| size_t reloc_addrs_size = 0; |
| void *last_addr = NULL; |
| unsigned char uleb128_buf[sizeof (size_t) * 2]; |
| for (void *addr : reloc_addrs_vec) |
| { |
| gcc_assert ((uintptr_t) addr >= (uintptr_t) mmi.preferred_base |
| && ((uintptr_t) addr + sizeof (void *) |
| < (uintptr_t) mmi.preferred_base + mmi.size)); |
| if (addr == last_addr) |
| continue; |
| if (last_addr == NULL) |
| last_addr = mmi.preferred_base; |
| size_t diff = (uintptr_t) addr - (uintptr_t) last_addr; |
| reloc_addrs_size += write_uleb128 (uleb128_buf, diff); |
| last_addr = addr; |
| } |
| if (fwrite (&reloc_addrs_size, sizeof (reloc_addrs_size), 1, f) != 1) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| last_addr = NULL; |
| for (void *addr : reloc_addrs_vec) |
| { |
| if (addr == last_addr) |
| continue; |
| if (last_addr == NULL) |
| last_addr = mmi.preferred_base; |
| size_t diff = (uintptr_t) addr - (uintptr_t) last_addr; |
| reloc_addrs_size = write_uleb128 (uleb128_buf, diff); |
| if (fwrite (uleb128_buf, 1, reloc_addrs_size, f) != reloc_addrs_size) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| last_addr = addr; |
| } |
| |
| ggc_pch_finish (state.d, state.f); |
| |
| gt_pch_fixup_stringpool (); |
| |
| unsigned num_callbacks = callback_vec.length (); |
| void (*pch_save) (FILE *) = >_pch_save; |
| if (fwrite (&pch_save, sizeof (pch_save), 1, f) != 1 |
| || fwrite (&num_callbacks, sizeof (num_callbacks), 1, f) != 1 |
| || (num_callbacks |
| && fwrite (callback_vec.address (), sizeof (void *), num_callbacks, |
| f) != num_callbacks)) |
| fatal_error (input_location, "cannot write PCH file: %m"); |
| |
| XDELETE (state.ptrs); |
| XDELETE (this_object); |
| delete saving_htab; |
| saving_htab = NULL; |
| callback_vec.release (); |
| reloc_addrs_vec.release (); |
| } |
| |
| /* Read the state of the compiler back in from F. */ |
| |
| void |
| gt_pch_restore (FILE *f) |
| { |
| const struct ggc_root_tab *const *rt; |
| const struct ggc_root_tab *rti; |
| size_t i; |
| struct mmap_info mmi; |
| int result; |
| |
| /* We are about to reload the line maps along with the rest of the PCH |
| data, which means that the (loaded) ones cannot be guaranteed to be |
| in any valid state for reporting diagnostics that happen during the |
| load. Save the current table (and use it during the loading process |
| below). */ |
| class line_maps *save_line_table = line_table; |
| |
| /* Delete any deletable objects. This makes ggc_pch_read much |
| faster, as it can be sure that no GCable objects remain other |
| than the ones just read in. */ |
| for (rt = gt_ggc_deletable_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| memset (rti->base, 0, rti->stride); |
| |
| /* Read in all the scalar variables. */ |
| for (rt = gt_pch_scalar_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| if (fread (rti->base, rti->stride, 1, f) != 1) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| /* Read in all the global pointers, in 6 easy loops. */ |
| bool error_reading_pointers = false; |
| for (rt = gt_ggc_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| for (i = 0; i < rti->nelt; i++) |
| if (fread ((char *)rti->base + rti->stride * i, |
| sizeof (void *), 1, f) != 1) |
| error_reading_pointers = true; |
| |
| /* Stash the newly read-in line table pointer - it does not point to |
| anything meaningful yet, so swap the old one back in. */ |
| class line_maps *new_line_table = line_table; |
| line_table = save_line_table; |
| if (error_reading_pointers) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| if (fread (&mmi, sizeof (mmi), 1, f) != 1) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| void *orig_preferred_base = mmi.preferred_base; |
| result = host_hooks.gt_pch_use_address (mmi.preferred_base, mmi.size, |
| fileno (f), mmi.offset); |
| |
| /* We could not mmap or otherwise allocate the required memory at the |
| address needed. */ |
| if (result < 0) |
| { |
| sorry_at (input_location, "PCH allocation failure"); |
| /* There is no point in continuing from here, we will only end up |
| with a crashed (most likely hanging) compiler. */ |
| exit (-1); |
| } |
| |
| /* (0) We allocated memory, but did not mmap the file, so we need to read |
| the data in manually. (>0) Otherwise the mmap succeed for the address |
| we wanted. */ |
| if (result == 0) |
| { |
| if (fseek (f, mmi.offset, SEEK_SET) != 0 |
| || fread (mmi.preferred_base, mmi.size, 1, f) != 1) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| } |
| else if (fseek (f, mmi.offset + mmi.size, SEEK_SET) != 0) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| size_t reloc_addrs_size; |
| if (fread (&reloc_addrs_size, sizeof (reloc_addrs_size), 1, f) != 1) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| if (orig_preferred_base != mmi.preferred_base) |
| { |
| uintptr_t bias |
| = (uintptr_t) mmi.preferred_base - (uintptr_t) orig_preferred_base; |
| |
| /* Adjust all the global pointers by bias. */ |
| line_table = new_line_table; |
| for (rt = gt_ggc_rtab; *rt; rt++) |
| for (rti = *rt; rti->base != NULL; rti++) |
| for (i = 0; i < rti->nelt; i++) |
| { |
| char *addr = (char *)rti->base + rti->stride * i; |
| char *p; |
| memcpy (&p, addr, sizeof (void *)); |
| if ((uintptr_t) p >= (uintptr_t) orig_preferred_base |
| && (uintptr_t) p < (uintptr_t) orig_preferred_base + mmi.size) |
| { |
| p = (char *) ((uintptr_t) p + bias); |
| memcpy (addr, &p, sizeof (void *)); |
| } |
| } |
| new_line_table = line_table; |
| line_table = save_line_table; |
| |
| /* And adjust all the pointers in the image by bias too. */ |
| char *addr = (char *) mmi.preferred_base; |
| unsigned char uleb128_buf[4096], *uleb128_ptr = uleb128_buf; |
| while (reloc_addrs_size != 0) |
| { |
| size_t this_size |
| = MIN (reloc_addrs_size, |
| (size_t) (4096 - (uleb128_ptr - uleb128_buf))); |
| if (fread (uleb128_ptr, 1, this_size, f) != this_size) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| unsigned char *uleb128_end = uleb128_ptr + this_size; |
| if (this_size != reloc_addrs_size) |
| uleb128_end -= 2 * sizeof (size_t); |
| uleb128_ptr = uleb128_buf; |
| while (uleb128_ptr < uleb128_end) |
| { |
| size_t diff; |
| uleb128_ptr = read_uleb128 (uleb128_ptr, &diff); |
| addr = (char *) ((uintptr_t) addr + diff); |
| |
| char *p; |
| memcpy (&p, addr, sizeof (void *)); |
| gcc_assert ((uintptr_t) p >= (uintptr_t) orig_preferred_base |
| && ((uintptr_t) p |
| < (uintptr_t) orig_preferred_base + mmi.size)); |
| p = (char *) ((uintptr_t) p + bias); |
| memcpy (addr, &p, sizeof (void *)); |
| } |
| reloc_addrs_size -= this_size; |
| if (reloc_addrs_size == 0) |
| break; |
| this_size = uleb128_end + 2 * sizeof (size_t) - uleb128_ptr; |
| memcpy (uleb128_buf, uleb128_ptr, this_size); |
| uleb128_ptr = uleb128_buf + this_size; |
| } |
| } |
| else if (fseek (f, (mmi.offset + mmi.size + sizeof (reloc_addrs_size) |
| + reloc_addrs_size), SEEK_SET) != 0) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| ggc_pch_read (f, mmi.preferred_base); |
| |
| void (*pch_save) (FILE *); |
| unsigned num_callbacks; |
| if (fread (&pch_save, sizeof (pch_save), 1, f) != 1 |
| || fread (&num_callbacks, sizeof (num_callbacks), 1, f) != 1) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| if (pch_save != >_pch_save) |
| { |
| uintptr_t binbias = (uintptr_t) >_pch_save - (uintptr_t) pch_save; |
| void **ptrs = XNEWVEC (void *, num_callbacks); |
| unsigned i; |
| uintptr_t bias |
| = (uintptr_t) mmi.preferred_base - (uintptr_t) orig_preferred_base; |
| |
| if (fread (ptrs, sizeof (void *), num_callbacks, f) != num_callbacks) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| for (i = 0; i < num_callbacks; ++i) |
| { |
| void *ptr = (void *) ((uintptr_t) ptrs[i] + bias); |
| memcpy (&pch_save, ptr, sizeof (pch_save)); |
| pch_save = (void (*) (FILE *)) ((uintptr_t) pch_save + binbias); |
| memcpy (ptr, &pch_save, sizeof (pch_save)); |
| } |
| XDELETE (ptrs); |
| } |
| else if (fseek (f, num_callbacks * sizeof (void *), SEEK_CUR) != 0) |
| fatal_error (input_location, "cannot read PCH file: %m"); |
| |
| gt_pch_restore_stringpool (); |
| |
| /* Barring corruption of the PCH file, the restored line table should be |
| complete and usable. */ |
| line_table = new_line_table; |
| } |
| |
| /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS when mmap is not present. |
| Select no address whatsoever, and let gt_pch_save choose what it will with |
| malloc, presumably. */ |
| |
| void * |
| default_gt_pch_get_address (size_t size ATTRIBUTE_UNUSED, |
| int fd ATTRIBUTE_UNUSED) |
| { |
| return NULL; |
| } |
| |
| /* Default version of HOST_HOOKS_GT_PCH_USE_ADDRESS when mmap is not present. |
| Allocate SIZE bytes with malloc. Return 0 if the address we got is the |
| same as base, indicating that the memory has been allocated but needs to |
| be read in from the file. Return -1 if the address differs, to relocation |
| of the PCH file would be required. */ |
| |
| int |
| default_gt_pch_use_address (void *&base, size_t size, int fd ATTRIBUTE_UNUSED, |
| size_t offset ATTRIBUTE_UNUSED) |
| { |
| void *addr = xmalloc (size); |
| return (addr == base) - 1; |
| } |
| |
| /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS. Return the |
| alignment required for allocating virtual memory. Usually this is the |
| same as pagesize. */ |
| |
| size_t |
| default_gt_pch_alloc_granularity (void) |
| { |
| return getpagesize (); |
| } |
| |
| #if HAVE_MMAP_FILE |
| /* Default version of HOST_HOOKS_GT_PCH_GET_ADDRESS when mmap is present. |
| We temporarily allocate SIZE bytes, and let the kernel place the data |
| wherever it will. If it worked, that's our spot, if not we're likely |
| to be in trouble. */ |
| |
| void * |
| mmap_gt_pch_get_address (size_t size, int fd) |
| { |
| void *ret; |
| |
| ret = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); |
| if (ret == (void *) MAP_FAILED) |
| ret = NULL; |
| else |
| munmap ((caddr_t) ret, size); |
| |
| return ret; |
| } |
| |
| /* Default version of HOST_HOOKS_GT_PCH_USE_ADDRESS when mmap is present. |
| Map SIZE bytes of FD+OFFSET at BASE. Return 1 if we succeeded at |
| mapping the data at BASE, -1 if we couldn't. |
| |
| This version assumes that the kernel honors the START operand of mmap |
| even without MAP_FIXED if START through START+SIZE are not currently |
| mapped with something. */ |
| |
| int |
| mmap_gt_pch_use_address (void *&base, size_t size, int fd, size_t offset) |
| { |
| void *addr; |
| |
| /* We're called with size == 0 if we're not planning to load a PCH |
| file at all. This allows the hook to free any static space that |
| we might have allocated at link time. */ |
| if (size == 0) |
| return -1; |
| |
| addr = mmap ((caddr_t) base, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, |
| fd, offset); |
| |
| return addr == base ? 1 : -1; |
| } |
| #endif /* HAVE_MMAP_FILE */ |
| |
| #if !defined ENABLE_GC_CHECKING && !defined ENABLE_GC_ALWAYS_COLLECT |
| |
| /* Modify the bound based on rlimits. */ |
| static double |
| ggc_rlimit_bound (double limit) |
| { |
| #if defined(HAVE_GETRLIMIT) |
| struct rlimit rlim; |
| # if defined (RLIMIT_AS) |
| /* RLIMIT_AS is what POSIX says is the limit on mmap. Presumably |
| any OS which has RLIMIT_AS also has a working mmap that GCC will use. */ |
| if (getrlimit (RLIMIT_AS, &rlim) == 0 |
| && rlim.rlim_cur != (rlim_t) RLIM_INFINITY |
| && rlim.rlim_cur < limit) |
| limit = rlim.rlim_cur; |
| # elif defined (RLIMIT_DATA) |
| /* ... but some older OSs bound mmap based on RLIMIT_DATA, or we |
| might be on an OS that has a broken mmap. (Others don't bound |
| mmap at all, apparently.) */ |
| if (getrlimit (RLIMIT_DATA, &rlim) == 0 |
| && rlim.rlim_cur != (rlim_t) RLIM_INFINITY |
| && rlim.rlim_cur < limit |
| /* Darwin has this horribly bogus default setting of |
| RLIMIT_DATA, to 6144Kb. No-one notices because RLIMIT_DATA |
| appears to be ignored. Ignore such silliness. If a limit |
| this small was actually effective for mmap, GCC wouldn't even |
| start up. */ |
| && rlim.rlim_cur >= 8 * ONE_M) |
| limit = rlim.rlim_cur; |
| # endif /* RLIMIT_AS or RLIMIT_DATA */ |
| #endif /* HAVE_GETRLIMIT */ |
| |
| return limit; |
| } |
| |
| /* Heuristic to set a default for GGC_MIN_EXPAND. */ |
| static int |
| ggc_min_expand_heuristic (void) |
| { |
| double min_expand = physmem_total (); |
| |
| /* Adjust for rlimits. */ |
| min_expand = ggc_rlimit_bound (min_expand); |
| |
| /* The heuristic is a percentage equal to 30% + 70%*(RAM/1GB), yielding |
| a lower bound of 30% and an upper bound of 100% (when RAM >= 1GB). */ |
| min_expand /= ONE_G; |
| min_expand *= 70; |
| min_expand = MIN (min_expand, 70); |
| min_expand += 30; |
| |
| return min_expand; |
| } |
| |
| /* Heuristic to set a default for GGC_MIN_HEAPSIZE. */ |
| static int |
| ggc_min_heapsize_heuristic (void) |
| { |
| double phys_kbytes = physmem_total (); |
| double limit_kbytes = ggc_rlimit_bound (phys_kbytes * 2); |
| |
| phys_kbytes /= ONE_K; /* Convert to Kbytes. */ |
| limit_kbytes /= ONE_K; |
| |
| /* The heuristic is RAM/8, with a lower bound of 4M and an upper |
| bound of 128M (when RAM >= 1GB). */ |
| phys_kbytes /= 8; |
| |
| #if defined(HAVE_GETRLIMIT) && defined (RLIMIT_RSS) |
| /* Try not to overrun the RSS limit while doing garbage collection. |
| The RSS limit is only advisory, so no margin is subtracted. */ |
| { |
| struct rlimit rlim; |
| if (getrlimit (RLIMIT_RSS, &rlim) == 0 |
| && rlim.rlim_cur != (rlim_t) RLIM_INFINITY) |
| phys_kbytes = MIN (phys_kbytes, rlim.rlim_cur / ONE_K); |
| } |
| # endif |
| |
| /* Don't blindly run over our data limit; do GC at least when the |
| *next* GC would be within 20Mb of the limit or within a quarter of |
| the limit, whichever is larger. If GCC does hit the data limit, |
| compilation will fail, so this tries to be conservative. */ |
| limit_kbytes = MAX (0, limit_kbytes - MAX (limit_kbytes / 4, 20 * ONE_K)); |
| limit_kbytes = (limit_kbytes * 100) / (110 + ggc_min_expand_heuristic ()); |
| phys_kbytes = MIN (phys_kbytes, limit_kbytes); |
| |
| phys_kbytes = MAX (phys_kbytes, 4 * ONE_K); |
| phys_kbytes = MIN (phys_kbytes, 128 * ONE_K); |
| |
| return phys_kbytes; |
| } |
| #endif |
| |
| void |
| init_ggc_heuristics (void) |
| { |
| #if !defined ENABLE_GC_CHECKING && !defined ENABLE_GC_ALWAYS_COLLECT |
| param_ggc_min_expand = ggc_min_expand_heuristic (); |
| param_ggc_min_heapsize = ggc_min_heapsize_heuristic (); |
| #endif |
| } |
| |
| /* GGC memory usage. */ |
| class ggc_usage: public mem_usage |
| { |
| public: |
| /* Default constructor. */ |
| ggc_usage (): m_freed (0), m_collected (0), m_overhead (0) {} |
| /* Constructor. */ |
| ggc_usage (size_t allocated, size_t times, size_t peak, |
| size_t freed, size_t collected, size_t overhead) |
| : mem_usage (allocated, times, peak), |
| m_freed (freed), m_collected (collected), m_overhead (overhead) {} |
| |
| /* Equality operator. */ |
| inline bool |
| operator== (const ggc_usage &second) const |
| { |
| return (get_balance () == second.get_balance () |
| && m_peak == second.m_peak |
| && m_times == second.m_times); |
| } |
| |
| /* Comparison operator. */ |
| inline bool |
| operator< (const ggc_usage &second) const |
| { |
| if (*this == second) |
| return false; |
| |
| return (get_balance () == second.get_balance () ? |
| (m_peak == second.m_peak ? m_times < second.m_times |
| : m_peak < second.m_peak) |
| : get_balance () < second.get_balance ()); |
| } |
| |
| /* Register overhead of ALLOCATED and OVERHEAD bytes. */ |
| inline void |
| register_overhead (size_t allocated, size_t overhead) |
| { |
| m_allocated += allocated; |
| m_overhead += overhead; |
| m_times++; |
| } |
| |
| /* Release overhead of SIZE bytes. */ |
| inline void |
| release_overhead (size_t size) |
| { |
| m_freed += size; |
| } |
| |
| /* Sum the usage with SECOND usage. */ |
| ggc_usage |
| operator+ (const ggc_usage &second) |
| { |
| return ggc_usage (m_allocated + second.m_allocated, |
| m_times + second.m_times, |
| m_peak + second.m_peak, |
| m_freed + second.m_freed, |
| m_collected + second.m_collected, |
| m_overhead + second.m_overhead); |
| } |
| |
| /* Dump usage with PREFIX, where TOTAL is sum of all rows. */ |
| inline void |
| dump (const char *prefix, ggc_usage &total) const |
| { |
| size_t balance = get_balance (); |
| fprintf (stderr, |
| "%-48s " PRsa (9) ":%5.1f%%" PRsa (9) ":%5.1f%%" |
| PRsa (9) ":%5.1f%%" PRsa (9) ":%5.1f%%" PRsa (9) "\n", |
| prefix, |
| SIZE_AMOUNT (balance), get_percent (balance, total.get_balance ()), |
| SIZE_AMOUNT (m_collected), |
| get_percent (m_collected, total.m_collected), |
| SIZE_AMOUNT (m_freed), get_percent (m_freed, total.m_freed), |
| SIZE_AMOUNT (m_overhead), |
| get_percent (m_overhead, total.m_overhead), |
| SIZE_AMOUNT (m_times)); |
| } |
| |
| /* Dump usage coupled to LOC location, where TOTAL is sum of all rows. */ |
| inline void |
| dump (mem_location *loc, ggc_usage &total) const |
| { |
| char *location_string = loc->to_string (); |
| |
| dump (location_string, total); |
| |
| free (location_string); |
| } |
| |
| /* Dump footer. */ |
| inline void |
| dump_footer () |
| { |
| dump ("Total", *this); |
| } |
| |
| /* Get balance which is GGC allocation leak. */ |
| inline size_t |
| get_balance () const |
| { |
| return m_allocated + m_overhead - m_collected - m_freed; |
| } |
| |
| typedef std::pair<mem_location *, ggc_usage *> mem_pair_t; |
| |
| /* Compare wrapper used by qsort method. */ |
| static int |
| compare (const void *first, const void *second) |
| { |
| const mem_pair_t mem1 = *(const mem_pair_t *) first; |
| const mem_pair_t mem2 = *(const mem_pair_t *) second; |
| |
| size_t balance1 = mem1.second->get_balance (); |
| size_t balance2 = mem2.second->get_balance (); |
| |
| return balance1 == balance2 ? 0 : (balance1 < balance2 ? 1 : -1); |
| } |
| |
| /* Dump header with NAME. */ |
| static inline void |
| dump_header (const char *name) |
| { |
| fprintf (stderr, "%-48s %11s%17s%17s%16s%17s\n", name, "Leak", "Garbage", |
| "Freed", "Overhead", "Times"); |
| } |
| |
| /* Freed memory in bytes. */ |
| size_t m_freed; |
| /* Collected memory in bytes. */ |
| size_t m_collected; |
| /* Overhead memory in bytes. */ |
| size_t m_overhead; |
| }; |
| |
| /* GCC memory description. */ |
| static mem_alloc_description<ggc_usage> ggc_mem_desc; |
| |
| /* Dump per-site memory statistics. */ |
| |
| void |
| dump_ggc_loc_statistics () |
| { |
| if (! GATHER_STATISTICS) |
| return; |
| |
| ggc_collect (GGC_COLLECT_FORCE); |
| |
| ggc_mem_desc.dump (GGC_ORIGIN); |
| } |
| |
| /* Record ALLOCATED and OVERHEAD bytes to descriptor NAME:LINE (FUNCTION). */ |
| void |
| ggc_record_overhead (size_t allocated, size_t overhead, void *ptr MEM_STAT_DECL) |
| { |
| ggc_usage *usage = ggc_mem_desc.register_descriptor (ptr, GGC_ORIGIN, false |
| FINAL_PASS_MEM_STAT); |
| |
| ggc_mem_desc.register_object_overhead (usage, allocated + overhead, ptr); |
| usage->register_overhead (allocated, overhead); |
| } |
| |
| /* Notice that the pointer has been freed. */ |
| void |
| ggc_free_overhead (void *ptr) |
| { |
| ggc_mem_desc.release_object_overhead (ptr); |
| } |
| |
| /* After live values has been marked, walk all recorded pointers and see if |
| they are still live. */ |
| void |
| ggc_prune_overhead_list (void) |
| { |
| typedef hash_map<const void *, std::pair<ggc_usage *, size_t > > map_t; |
| |
| map_t::iterator it = ggc_mem_desc.m_reverse_object_map->begin (); |
| |
| for (; it != ggc_mem_desc.m_reverse_object_map->end (); ++it) |
| if (!ggc_marked_p ((*it).first)) |
| { |
| (*it).second.first->m_collected += (*it).second.second; |
| ggc_mem_desc.m_reverse_object_map->remove ((*it).first); |
| } |
| } |
| |
| /* Print memory used by heap if this info is available. */ |
| |
| void |
| report_heap_memory_use () |
| { |
| #if defined(HAVE_MALLINFO) || defined(HAVE_MALLINFO2) |
| #ifdef HAVE_MALLINFO2 |
| #define MALLINFO_FN mallinfo2 |
| #else |
| #define MALLINFO_FN mallinfo |
| #endif |
| if (!quiet_flag) |
| fprintf (stderr, " {heap " PRsa (0) "}", |
| SIZE_AMOUNT (MALLINFO_FN ().arena)); |
| #endif |
| } |