Re: [PATCH 2/3] statistics: Fix destruction ordering issues with mem_alloc_description
Richard Biener <[email protected]> Mon, 3 Aug 2026 09:07:47 +0200
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CAFiYyc0RvjOAw4vgRZjr+jpddMt8gSAeYd2f7KUq7PEt9smzRw@mail.gmail.com> |
On Sat, Aug 1, 2026 at 4:04 PM Lewis Hyatt <[email protected]> wrote: > > The previous patch fixed the documented issue PR28734 with > --enable-gather-detailed-mem-stats, but there remain a couple other issues > not previously reported: > > a) Around 200 tests still fail with statistics gathering enabled, > including: > gfortran.dg/gomp/class-firstprivate-1.f90 -O (internal compiler error: in check_complete_insertion, at hash-table.h:572) gfortran.sum > as well as most GCOV and JIT tests. > > b) bootstrap fails when configured with: > --enable-gather-detailed-mem-stats --with-build-config=bootstrap-lto > with similar ICEs in lots of places during stage 2. > > These both have the same cause. The classes that implement the statistics > gathering are file-local static variables that are referenced from the > destructors of the data structures (hash_map, vec, ggc, etc) that they are > tracking. Consequently, any static instance of such a data structure is > subject to destruction order issues; if the mem_alloc_description is > destroyed before the static object it is tracking, then problems will arise. > > It seems that the cleanest solution is to arrange that mem_alloc_description > objects are never destroyed, given that they persist anyway for the duration > of the process. It does not seem desirable to forbid static instances of all > data structures, since these seem to be used in many places. OK. Thanks, Richard. > gcc/ChangeLog: > > * mem-stats.h (mem_alloc_description::mem_alloc_description): Make > private. > (mem_alloc_description::~mem_alloc_description): Disallow > destruction entirely. > (mem_alloc_description::instance): New function. > * alloc-pool.cc (dump_alloc_pool_statistics): Adapt to > pool_allocator_usage being a function instead of a variable. > * alloc-pool.h (pool_allocator_usage): New function. > (TBlockAllocator>::initialize): Adapt to > pool_allocator_usage being a function instead of a variable. > (TBlockAllocator>::release): Likewise. > (TBlockAllocator>::allocate): Likewise. > (TBlockAllocator>::remove): Likewise. > * bitmap.cc (bitmap_mem_desc): Change from a static variable to an > inline function. > (bitmap_register): Adapt to bitmap_mem_desc being a function instead > of a variable. > (register_overhead): Likewise. > (release_overhead): Likewise. > (bitmap_list_find_element): Likewise. > (bitmap_tree_splay): Likewise. > (bitmap_tree_find_element): Likewise. > (dump_bitmap_statistics): Likewise. > * bitmap.h (bitmap_mem_desc): Remove unneeded extern declaration. > * ggc-common.cc (ggc_mem_desc): Change from a static variable to an > inline function. > (ggc_record_overhead): Adapt to ggc_mem_desc being a function > instead of a variable. > (ggc_free_overhead): Likewise > (ggc_prune_overhead_list): Likewise. > * vec.cc (vec_mem_desc): Change from a static variable to a > function. > (vec_prefix::register_overhead): Adapt to vec_mem_desc being a > function instead of a variable. > (vec_prefix::release_overhead): Likewise. > (dump_vec_loc_statistics): Likewise. > * hash-table.cc (hash_table_usage): Move to... > * hash-table.h (hash_table_usage): ...here, and use new singleton > interface. > --- > gcc/alloc-pool.cc | 3 +-- > gcc/alloc-pool.h | 14 +++++++++----- > gcc/bitmap.cc | 26 +++++++++++++++----------- > gcc/bitmap.h | 3 --- > gcc/ggc-common.cc | 22 +++++++++++++--------- > gcc/hash-table.cc | 11 ----------- > gcc/hash-table.h | 6 +++++- > gcc/mem-stats.h | 41 ++++++++++++++++++----------------------- > gcc/vec.cc | 22 +++++++++++++--------- > 9 files changed, 74 insertions(+), 74 deletions(-) > > diff --git a/gcc/alloc-pool.cc b/gcc/alloc-pool.cc > index 8f35fee6821..cb69b326271 100644 > --- a/gcc/alloc-pool.cc > +++ b/gcc/alloc-pool.cc > @@ -24,7 +24,6 @@ along with GCC; see the file COPYING3. If not see > #include "alloc-pool.h" > > ALLOC_POOL_ID_TYPE last_id; > -mem_alloc_description<pool_usage> pool_allocator_usage; > bool after_memory_report = false; > > /* Output per-alloc_pool memory usage statistics. */ > @@ -34,5 +33,5 @@ dump_alloc_pool_statistics (void) > if (! GATHER_STATISTICS) > return; > > - pool_allocator_usage.dump (ALLOC_POOL_ORIGIN); > + pool_allocator_usage ().dump (ALLOC_POOL_ORIGIN); > } > diff --git a/gcc/alloc-pool.h b/gcc/alloc-pool.h > index f81c1794137..3c801789edf 100644 > --- a/gcc/alloc-pool.h > +++ b/gcc/alloc-pool.h > @@ -100,7 +100,11 @@ public: > const char *m_pool_name; > }; > > -extern mem_alloc_description<pool_usage> pool_allocator_usage; > +inline auto & > +pool_allocator_usage () > +{ > + return mem_alloc_description<pool_usage>::instance<ALLOC_POOL_ORIGIN> (); > +} > > #if 0 > /* If a pool with custom block size is needed, one might use the following > @@ -274,7 +278,7 @@ base_pool_allocator <TBlockAllocator>::initialize () > > if (GATHER_STATISTICS) > { > - pool_usage *u = pool_allocator_usage.register_descriptor > + pool_usage *u = pool_allocator_usage ().register_descriptor > (this, new mem_location (m_location)); > > u->m_element_size = m_elt_size; > @@ -315,7 +319,7 @@ base_pool_allocator <TBlockAllocator>::release () > > if (GATHER_STATISTICS && !after_memory_report) > { > - pool_allocator_usage.release_instance_overhead > + pool_allocator_usage ().release_instance_overhead > (this, (m_elts_allocated - m_elts_free) * m_elt_size); > } > > @@ -357,7 +361,7 @@ base_pool_allocator <TBlockAllocator>::allocate () > > if (GATHER_STATISTICS) > { > - pool_allocator_usage.register_instance_overhead (m_elt_size, this); > + pool_allocator_usage ().register_instance_overhead (m_elt_size, this); > } > > #ifdef ENABLE_VALGRIND_ANNOTATIONS > @@ -458,7 +462,7 @@ base_pool_allocator <TBlockAllocator>::remove (void *object) > > if (GATHER_STATISTICS) > { > - pool_allocator_usage.release_instance_overhead (this, m_elt_size); > + pool_allocator_usage ().release_instance_overhead (this, m_elt_size); > } > } > > diff --git a/gcc/bitmap.cc b/gcc/bitmap.cc > index 6bf0ee99d04..d896a551394 100644 > --- a/gcc/bitmap.cc > +++ b/gcc/bitmap.cc > @@ -40,7 +40,11 @@ using bitmap_splay_tree > = splay_tree_without_parent<bitmap_splay_tree_accessors>; > > /* Memory allocation statistics purpose instance. */ > -mem_alloc_description<bitmap_usage> bitmap_mem_desc; > +inline auto & > +bitmap_mem_desc () > +{ > + return mem_alloc_description<bitmap_usage>::instance<BITMAP_ORIGIN> (); > +} > > /* Static zero-initialized bitmap obstack used for default initialization > of bitmap_head. */ > @@ -54,8 +58,8 @@ bitmap_register (bitmap b MEM_STAT_DECL) > gcc_assert (b->alloc_descriptor == 0); > b->alloc_descriptor = alloc_descriptor_max_uid++; > > - bitmap_mem_desc.register_descriptor (b->get_descriptor (), BITMAP_ORIGIN, > - false FINAL_PASS_MEM_STAT); > + bitmap_mem_desc ().register_descriptor (b->get_descriptor (), BITMAP_ORIGIN, > + false FINAL_PASS_MEM_STAT); > } > > /* Account the overhead. */ > @@ -63,8 +67,8 @@ static void > register_overhead (bitmap b, size_t amount) > { > unsigned *d = b->get_descriptor (); > - if (bitmap_mem_desc.contains_descriptor_for_instance (d)) > - bitmap_mem_desc.register_instance_overhead (amount, d); > + if (bitmap_mem_desc ().contains_descriptor_for_instance (d)) > + bitmap_mem_desc ().register_instance_overhead (amount, d); > } > > /* Release the overhead. */ > @@ -72,8 +76,8 @@ static void > release_overhead (bitmap b, size_t amount, bool remove_from_map) > { > unsigned *d = b->get_descriptor (); > - if (bitmap_mem_desc.contains_descriptor_for_instance (d)) > - bitmap_mem_desc.release_instance_overhead (d, amount, remove_from_map); > + if (bitmap_mem_desc ().contains_descriptor_for_instance (d)) > + bitmap_mem_desc ().release_instance_overhead (d, amount, remove_from_map); > } > > > @@ -374,7 +378,7 @@ bitmap_list_find_element (bitmap head, unsigned int indx) > call initialize function. */ > bitmap_usage *usage = NULL; > if (GATHER_STATISTICS) > - usage = bitmap_mem_desc.get_descriptor_for_instance (head); > + usage = bitmap_mem_desc ().get_descriptor_for_instance (head); > > /* This bitmap has more than one element, and we're going to look > through the elements list. Count that as a search. */ > @@ -481,7 +485,7 @@ bitmap_tree_splay (bitmap head, bitmap_element *t, unsigned int indx) > > bitmap_usage *usage = NULL; > if (GATHER_STATISTICS) > - usage = bitmap_mem_desc.get_descriptor_for_instance (head); > + usage = bitmap_mem_desc ().get_descriptor_for_instance (head); > > N.prev = N.next = NULL; > l = r = &N; > @@ -587,7 +591,7 @@ bitmap_tree_find_element (bitmap head, unsigned int indx) > call initialize function. */ > bitmap_usage *usage = NULL; > if (GATHER_STATISTICS) > - usage = bitmap_mem_desc.get_descriptor_for_instance (head); > + usage = bitmap_mem_desc ().get_descriptor_for_instance (head); > > /* This bitmap has more than one element, and we're going to look > through the elements list. Count that as a search. */ > @@ -2857,7 +2861,7 @@ dump_bitmap_statistics (void) > if (!GATHER_STATISTICS) > return; > > - bitmap_mem_desc.dump (BITMAP_ORIGIN); > + bitmap_mem_desc ().dump (BITMAP_ORIGIN); > } > > DEBUG_FUNCTION void > diff --git a/gcc/bitmap.h b/gcc/bitmap.h > index c6fd91ee90a..88ff5cf020e 100644 > --- a/gcc/bitmap.h > +++ b/gcc/bitmap.h > @@ -272,9 +272,6 @@ public: > uint64_t m_search_iter; > }; > > -/* Bitmap memory description. */ > -extern mem_alloc_description<bitmap_usage> bitmap_mem_desc; > - > /* Fundamental storage type for bitmap. */ > > typedef unsigned long BITMAP_WORD; > diff --git a/gcc/ggc-common.cc b/gcc/ggc-common.cc > index 6d3dca78f03..8752fad81b7 100644 > --- a/gcc/ggc-common.cc > +++ b/gcc/ggc-common.cc > @@ -1269,7 +1269,11 @@ public: > }; > > /* GCC memory description. */ > -static mem_alloc_description<ggc_usage> ggc_mem_desc; > +inline auto & > +ggc_mem_desc () > +{ > + return mem_alloc_description<ggc_usage>::instance<GGC_ORIGIN> (); > +} > > /* Dump per-site memory statistics. */ > > @@ -1281,17 +1285,17 @@ dump_ggc_loc_statistics () > > ggc_collect (GGC_COLLECT_FORCE); > > - ggc_mem_desc.dump (GGC_ORIGIN); > + 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_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); > + ggc_mem_desc ().register_object_overhead (usage, allocated + overhead, ptr); > usage->register_overhead (allocated, overhead); > } > > @@ -1299,7 +1303,7 @@ ggc_record_overhead (size_t allocated, size_t overhead, void *ptr MEM_STAT_DECL) > void > ggc_free_overhead (void *ptr) > { > - ggc_mem_desc.release_object_overhead (ptr); > + ggc_mem_desc ().release_object_overhead (ptr); > } > > /* After live values has been marked, walk all recorded pointers and see if > @@ -1309,13 +1313,13 @@ 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 (); > + map_t::iterator it = ggc_mem_desc ().m_reverse_object_map->begin (); > > - for (; it != ggc_mem_desc.m_reverse_object_map->end (); ++it) > + 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); > + ggc_mem_desc ().m_reverse_object_map->remove ((*it).first); > } > } > > diff --git a/gcc/hash-table.cc b/gcc/hash-table.cc > index 22a08b2735b..20650d1ac53 100644 > --- a/gcc/hash-table.cc > +++ b/gcc/hash-table.cc > @@ -101,17 +101,6 @@ hash_table_higher_prime_index (unsigned long n) > return low; > } > > -/* Return a reference to the lazily initialized hash-table usage description. > - This needs to be a function rather than a simple global variable so that it > - is reliably initialized before hash table variables in other files such as > - sem_item::m_type_hash_cache. */ > -mem_alloc_description<mem_usage>& > -hash_table_usage () > -{ > - static mem_alloc_description<mem_usage> usage; > - return usage; > -} > - > /* Support function for statistics. */ > void dump_hash_table_loc_statistics (void) > { > diff --git a/gcc/hash-table.h b/gcc/hash-table.h > index 0bee2850b8c..664c3e0ce45 100644 > --- a/gcc/hash-table.h > +++ b/gcc/hash-table.h > @@ -636,7 +636,11 @@ private: > #include "mem-stats.h" > #include "hash-map.h" > > -extern mem_alloc_description<mem_usage>& hash_table_usage (void); > +inline auto & > +hash_table_usage () > +{ > + return mem_alloc_description<mem_usage>::instance<HASH_TABLE_ORIGIN> (); > +} > > /* Support function for statistics. */ > extern void dump_hash_table_loc_statistics (void); > diff --git a/gcc/mem-stats.h b/gcc/mem-stats.h > index 2d4de15ccff..dd83c919877 100644 > --- a/gcc/mem-stats.h > +++ b/gcc/mem-stats.h > @@ -276,7 +276,23 @@ public: > template <class T> > class mem_alloc_description > { > + > + /* Constructor is private to enforce singleton. */ > + mem_alloc_description (); > + > + /* Destruction is not allowed, since we might be tracking > + static objects with undefined destruction order. */ > + ~mem_alloc_description () = delete; > + > public: > + > + template<mem_alloc_origin> > + static auto &instance () > + { > + static const auto self = new mem_alloc_description; > + return *self; > + } > + > struct mem_location_hash : nofree_ptr_hash <mem_location> > { > static hashval_t > @@ -306,11 +322,6 @@ public: > typedef hash_map <const void *, std::pair<T *, size_t> > reverse_object_map_t; > typedef std::pair <mem_location *, T *> mem_list_t; > > - /* Default constructor. */ > - mem_alloc_description (); > - > - /* Default destructor. */ > - ~mem_alloc_description (); > > /* Returns true if instance PTR is registered by the memory description. */ > bool contains_descriptor_for_instance (const void *ptr); > @@ -558,29 +569,13 @@ template <class T> > inline > mem_alloc_description<T>::mem_alloc_description () > { > + /* Note it is important to pass false for the 4th argument (GATHER_MEM_STATS) > + to avoid infinite recursion in instance (). */ > m_map = new mem_map_t (13, false, false, false); > m_reverse_map = new reverse_mem_map_t (13, false, false, false); > m_reverse_object_map = new reverse_object_map_t (13, false, false, false); > } > > -/* Default destructor. */ > - > -template <class T> > -inline > -mem_alloc_description<T>::~mem_alloc_description () > -{ > - for (typename mem_map_t::iterator it = m_map->begin (); it != m_map->end (); > - ++it) > - { > - delete (*it).first; > - delete (*it).second; > - } > - > - delete m_map; > - delete m_reverse_map; > - delete m_reverse_object_map; > -} > - > /* Get all tracked instances registered by the description. Items are filtered > by ORIGIN type, LENGTH is return value where we register the number of > elements in the list. If we want to process custom order, CMP comparator > diff --git a/gcc/vec.cc b/gcc/vec.cc > index 0740e495848..8313adde2b2 100644 > --- a/gcc/vec.cc > +++ b/gcc/vec.cc > @@ -111,7 +111,11 @@ public: > }; > > /* Vector memory description. */ > -static mem_alloc_description <vec_usage> vec_mem_desc; > +inline auto & > +vec_mem_desc () > +{ > + return mem_alloc_description<vec_usage>::instance<VEC_ORIGIN> (); > +} > > /* Account the overhead. */ > > @@ -119,10 +123,10 @@ void > vec_prefix::register_overhead (void *ptr, size_t elements, > size_t element_size MEM_STAT_DECL) > { > - vec_mem_desc.register_descriptor (ptr, VEC_ORIGIN, false > - FINAL_PASS_MEM_STAT); > + vec_mem_desc ().register_descriptor (ptr, VEC_ORIGIN, false > + FINAL_PASS_MEM_STAT); > vec_usage *usage > - = vec_mem_desc.register_instance_overhead (elements * element_size, ptr); > + = vec_mem_desc ().register_instance_overhead (elements * element_size, ptr); > usage->m_element_size = element_size; > usage->m_items += elements; > if (usage->m_items_peak < usage->m_items) > @@ -135,11 +139,11 @@ void > vec_prefix::release_overhead (void *ptr, size_t size, size_t elements, > bool in_dtor MEM_STAT_DECL) > { > - if (!vec_mem_desc.contains_descriptor_for_instance (ptr)) > - vec_mem_desc.register_descriptor (ptr, VEC_ORIGIN, > + if (!vec_mem_desc ().contains_descriptor_for_instance (ptr)) > + vec_mem_desc ().register_descriptor (ptr, VEC_ORIGIN, > false FINAL_PASS_MEM_STAT); > - vec_usage *usage = vec_mem_desc.release_instance_overhead (ptr, size, > - in_dtor); > + vec_usage *usage = vec_mem_desc ().release_instance_overhead (ptr, size, > + in_dtor); > usage->m_items -= elements; > } > > @@ -173,7 +177,7 @@ vec_prefix::calculate_allocation_1 (unsigned alloc, unsigned desired) > void > dump_vec_loc_statistics (void) > { > - vec_mem_desc.dump (VEC_ORIGIN); > + vec_mem_desc ().dump (VEC_ORIGIN); > } > > /* Gets the next token from STR delimited by DELIMS (deliminator not included