1 /* A memory statistics tracking infrastructure.
2    Copyright (C) 2015-2021 Free Software Foundation, Inc.
3    Contributed by Martin Liska  <mliska@suse.cz>
4 
5 This file is part of GCC.
6 
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
10 version.
11 
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3.  If not see
19 <http://www.gnu.org/licenses/>.  */
20 
21 #ifndef GCC_MEM_STATS_H
22 #define GCC_MEM_STATS_H
23 
24 /* Forward declaration.  */
25 template<typename Key, typename Value,
26 	 typename Traits = simple_hashmap_traits<default_hash_traits<Key>,
27 						 Value> >
28 class hash_map;
29 
30 #define LOCATION_LINE_EXTRA_SPACE 30
31 #define LOCATION_LINE_WIDTH	  48
32 
33 /* Memory allocation location.  */
34 class mem_location
35 {
36 public:
37   /* Default constructor.  */
38   inline
mem_location()39   mem_location () {}
40 
41   /* Constructor.  */
42   inline
43   mem_location (mem_alloc_origin origin, bool ggc,
44 		const char *filename = NULL, int line = 0,
45 		const char *function = NULL):
m_filename(filename)46     m_filename (filename), m_function (function), m_line (line), m_origin
47     (origin), m_ggc (ggc) {}
48 
49   /* Copy constructor.  */
50   inline
mem_location(mem_location & other)51   mem_location (mem_location &other): m_filename (other.m_filename),
52     m_function (other.m_function), m_line (other.m_line),
53     m_origin (other.m_origin), m_ggc (other.m_ggc) {}
54 
55   /* Compute hash value based on file name, function name and line in
56      source code. As there is just a single pointer registered for every
57      constant that points to e.g. the same file name, we can use hash
58      of the pointer.  */
59   hashval_t
hash()60   hash ()
61   {
62     inchash::hash hash;
63 
64     hash.add_ptr (m_filename);
65     hash.add_ptr (m_function);
66     hash.add_int (m_line);
67 
68     return hash.end ();
69   }
70 
71   /* Return true if the memory location is equal to OTHER.  */
72   int
equal(const mem_location & other)73   equal (const mem_location &other)
74   {
75     return m_filename == other.m_filename && m_function == other.m_function
76       && m_line == other.m_line;
77   }
78 
79   /* Return trimmed filename for the location.  */
80   inline const char *
get_trimmed_filename()81   get_trimmed_filename ()
82   {
83     const char *s1 = m_filename;
84     const char *s2;
85 
86     while ((s2 = strstr (s1, "gcc/")))
87       s1 = s2 + 4;
88 
89     return s1;
90   }
91 
92   inline char *
to_string()93   to_string ()
94   {
95     unsigned l = strlen (get_trimmed_filename ()) + strlen (m_function)
96       + LOCATION_LINE_EXTRA_SPACE;
97 
98     char *s = XNEWVEC (char, l);
99     sprintf (s, "%s:%i (%s)", get_trimmed_filename (),
100 	     m_line, m_function);
101 
102     s[MIN (LOCATION_LINE_WIDTH, l - 1)] = '\0';
103 
104     return s;
105   }
106 
107   /* Return display name associated to ORIGIN type.  */
108   static const char *
get_origin_name(mem_alloc_origin origin)109   get_origin_name (mem_alloc_origin origin)
110   {
111     return mem_alloc_origin_names[(unsigned) origin];
112   }
113 
114   /* File name of source code.  */
115   const char *m_filename;
116   /* Funcation name.  */
117   const char *m_function;
118   /* Line number in source code.  */
119   int m_line;
120   /* Origin type.  */
121   mem_alloc_origin m_origin;
122   /* Flag if used by GGC allocation.  */
123   bool m_ggc;
124 };
125 
126 /* Memory usage register to a memory location.  */
127 class mem_usage
128 {
129 public:
130   /* Default constructor.  */
mem_usage()131   mem_usage (): m_allocated (0), m_times (0), m_peak (0), m_instances (1) {}
132 
133   /* Constructor.  */
134   mem_usage (size_t allocated, size_t times, size_t peak, size_t instances = 0):
m_allocated(allocated)135     m_allocated (allocated), m_times (times), m_peak (peak),
136     m_instances (instances) {}
137 
138   /* Register overhead of SIZE bytes.  */
139   inline void
register_overhead(size_t size)140   register_overhead (size_t size)
141   {
142     m_allocated += size;
143     m_times++;
144 
145     if (m_peak < m_allocated)
146       m_peak = m_allocated;
147   }
148 
149   /* Release overhead of SIZE bytes.  */
150   inline void
release_overhead(size_t size)151   release_overhead (size_t size)
152   {
153     gcc_assert (size <= m_allocated);
154 
155     m_allocated -= size;
156   }
157 
158   /* Sum the usage with SECOND usage.  */
159   mem_usage
160   operator+ (const mem_usage &second)
161   {
162     return mem_usage (m_allocated + second.m_allocated,
163 		      m_times + second.m_times,
164 		      m_peak + second.m_peak,
165 		      m_instances + second.m_instances);
166   }
167 
168   /* Equality operator.  */
169   inline bool
170   operator== (const mem_usage &second) const
171   {
172     return (m_allocated == second.m_allocated
173 	    && m_peak == second.m_peak
174 	    && m_times == second.m_times);
175   }
176 
177   /* Comparison operator.  */
178   inline bool
179   operator< (const mem_usage &second) const
180   {
181     if (*this == second)
182       return false;
183 
184     return (m_allocated == second.m_allocated ?
185 	    (m_peak == second.m_peak ? m_times < second.m_times
186 	     : m_peak < second.m_peak) : m_allocated < second.m_allocated);
187   }
188 
189   /* Compare wrapper used by qsort method.  */
190   static int
compare(const void * first,const void * second)191   compare (const void *first, const void *second)
192   {
193     typedef std::pair<mem_location *, mem_usage *> mem_pair_t;
194 
195     const mem_pair_t f = *(const mem_pair_t *)first;
196     const mem_pair_t s = *(const mem_pair_t *)second;
197 
198     if (*f.second == *s.second)
199       return 0;
200 
201     return *f.second < *s.second ? 1 : -1;
202   }
203 
204   /* Dump usage coupled to LOC location, where TOTAL is sum of all rows.  */
205   inline void
dump(mem_location * loc,const mem_usage & total)206   dump (mem_location *loc, const mem_usage &total) const
207   {
208     char *location_string = loc->to_string ();
209 
210     fprintf (stderr, "%-48s " PRsa (9) ":%5.1f%%"
211 	     PRsa (9) PRsa (9) ":%5.1f%%%10s\n",
212 	     location_string, SIZE_AMOUNT (m_allocated),
213 	     get_percent (m_allocated, total.m_allocated),
214 	     SIZE_AMOUNT (m_peak), SIZE_AMOUNT (m_times),
215 	     get_percent (m_times, total.m_times), loc->m_ggc ? "ggc" : "heap");
216 
217     free (location_string);
218   }
219 
220   /* Dump footer.  */
221   inline void
dump_footer()222   dump_footer () const
223   {
224     fprintf (stderr, "%s" PRsa (53) PRsa (26) "\n", "Total",
225 	     SIZE_AMOUNT (m_allocated), SIZE_AMOUNT (m_times));
226   }
227 
228   /* Return fraction of NOMINATOR and DENOMINATOR in percent.  */
229   static inline float
get_percent(size_t nominator,size_t denominator)230   get_percent (size_t nominator, size_t denominator)
231   {
232     return denominator == 0 ? 0.0f : nominator * 100.0 / denominator;
233   }
234 
235   /* Print line made of dashes.  */
236   static inline void
237   print_dash_line (size_t count = 140)
238   {
239     while (count--)
240       fputc ('-', stderr);
241     fputc ('\n', stderr);
242   }
243 
244   /* Dump header with NAME.  */
245   static inline void
dump_header(const char * name)246   dump_header (const char *name)
247   {
248     fprintf (stderr, "%-48s %11s%16s%10s%17s\n", name, "Leak", "Peak",
249 	     "Times", "Type");
250   }
251 
252   /* Current number of allocated bytes.  */
253   size_t m_allocated;
254   /* Number of allocations.  */
255   size_t m_times;
256   /* Peak allocation in bytes.  */
257   size_t m_peak;
258   /* Number of container instances.  */
259   size_t m_instances;
260 };
261 
262 /* Memory usage pair that connectes memory usage and number
263    of allocated bytes.  */
264 template <class T>
265 class mem_usage_pair
266 {
267 public:
mem_usage_pair(T * usage_,size_t allocated_)268   mem_usage_pair (T *usage_, size_t allocated_): usage (usage_),
269   allocated (allocated_) {}
270 
271   T *usage;
272   size_t allocated;
273 };
274 
275 /* Memory allocation description.  */
276 template <class T>
277 class mem_alloc_description
278 {
279 public:
280   struct mem_location_hash : nofree_ptr_hash <mem_location>
281   {
282     static hashval_t
hashmem_location_hash283     hash (value_type l)
284     {
285       inchash::hash hstate;
286 
287       hstate.add_ptr ((const void *)l->m_filename);
288       hstate.add_ptr (l->m_function);
289       hstate.add_int (l->m_line);
290 
291       return hstate.end ();
292     }
293 
294     static bool
equalmem_location_hash295     equal (value_type l1, value_type l2)
296     {
297       return (l1->m_filename == l2->m_filename
298 	      && l1->m_function == l2->m_function
299 	      && l1->m_line == l2->m_line);
300     }
301   };
302 
303   /* Internal class type definitions.  */
304   typedef hash_map <mem_location_hash, T *> mem_map_t;
305   typedef hash_map <const void *, mem_usage_pair<T> > reverse_mem_map_t;
306   typedef hash_map <const void *, std::pair<T *, size_t> > reverse_object_map_t;
307   typedef std::pair <mem_location *, T *> mem_list_t;
308 
309   /* Default contructor.  */
310   mem_alloc_description ();
311 
312   /* Default destructor.  */
313   ~mem_alloc_description ();
314 
315   /* Returns true if instance PTR is registered by the memory description.  */
316   bool contains_descriptor_for_instance (const void *ptr);
317 
318   /* Return descriptor for instance PTR.  */
319   T *get_descriptor_for_instance (const void *ptr);
320 
321   /* Register memory allocation descriptor for container PTR which is
322      described by a memory LOCATION.  */
323   T *register_descriptor (const void *ptr, mem_location *location);
324 
325   /* Register memory allocation descriptor for container PTR.  ORIGIN identifies
326      type of container and GGC identifes if the allocation is handled in GGC
327      memory.  Each location is identified by file NAME, LINE in source code and
328      FUNCTION name.  */
329   T *register_descriptor (const void *ptr, mem_alloc_origin origin,
330 			  bool ggc, const char *name, int line,
331 			  const char *function);
332 
333   /* Register instance overhead identified by PTR pointer. Allocation takes
334      SIZE bytes.  */
335   T *register_instance_overhead (size_t size, const void *ptr);
336 
337   /* For containers (and GGC) where we want to track every instance object,
338      we register allocation of SIZE bytes, identified by PTR pointer, belonging
339      to USAGE descriptor.  */
340   void register_object_overhead (T *usage, size_t size, const void *ptr);
341 
342   /* Release PTR pointer of SIZE bytes. If REMOVE_FROM_MAP is set to true,
343      remove the instance from reverse map.  Return memory usage that belongs
344      to this memory description.  */
345   T *release_instance_overhead (void *ptr, size_t size,
346 				bool remove_from_map = false);
347 
348   /* Release instance object identified by PTR pointer.  */
349   void release_object_overhead (void *ptr);
350 
351   /* Unregister a memory allocation descriptor registered with
352      register_descriptor (remove from reverse map), unless it is
353      unregistered through release_instance_overhead with
354      REMOVE_FROM_MAP = true.  */
355   void unregister_descriptor (void *ptr);
356 
357   /* Get sum value for ORIGIN type of allocation for the descriptor.  */
358   T get_sum (mem_alloc_origin origin);
359 
360   /* Get all tracked instances registered by the description. Items
361      are filtered by ORIGIN type, LENGTH is return value where we register
362      the number of elements in the list. If we want to process custom order,
363      CMP comparator can be provided.  */
364   mem_list_t *get_list (mem_alloc_origin origin, unsigned *length);
365 
366   /* Dump all tracked instances of type ORIGIN. If we want to process custom
367      order, CMP comparator can be provided.  */
368   void dump (mem_alloc_origin origin);
369 
370   /* Reverse object map used for every object allocation mapping.  */
371   reverse_object_map_t *m_reverse_object_map;
372 
373 private:
374   /* Register overhead of SIZE bytes of ORIGIN type. PTR pointer is allocated
375      in NAME source file, at LINE in source code, in FUNCTION.  */
376   T *register_overhead (size_t size, mem_alloc_origin origin, const char *name,
377 			int line, const char *function, const void *ptr);
378 
379   /* Allocation location coupled to the description.  */
380   mem_location m_location;
381 
382   /* Location to usage mapping.  */
383   mem_map_t *m_map;
384 
385   /* Reverse pointer to usage mapping.  */
386   reverse_mem_map_t *m_reverse_map;
387 };
388 
389 /* Returns true if instance PTR is registered by the memory description.  */
390 
391 template <class T>
392 inline bool
contains_descriptor_for_instance(const void * ptr)393 mem_alloc_description<T>::contains_descriptor_for_instance (const void *ptr)
394 {
395   return m_reverse_map->get (ptr);
396 }
397 
398 /* Return descriptor for instance PTR.  */
399 
400 template <class T>
401 inline T*
get_descriptor_for_instance(const void * ptr)402 mem_alloc_description<T>::get_descriptor_for_instance (const void *ptr)
403 {
404   return m_reverse_map->get (ptr) ? (*m_reverse_map->get (ptr)).usage : NULL;
405 }
406 
407 /* Register memory allocation descriptor for container PTR which is
408    described by a memory LOCATION.  */
409 
410 template <class T>
411 inline T*
register_descriptor(const void * ptr,mem_location * location)412 mem_alloc_description<T>::register_descriptor (const void *ptr,
413 					       mem_location *location)
414 {
415   T *usage = NULL;
416 
417   T **slot = m_map->get (location);
418   if (slot)
419     {
420       delete location;
421       usage = *slot;
422       usage->m_instances++;
423     }
424   else
425     {
426       usage = new T ();
427       m_map->put (location, usage);
428     }
429 
430   if (!m_reverse_map->get (ptr))
431     m_reverse_map->put (ptr, mem_usage_pair<T> (usage, 0));
432 
433   return usage;
434 }
435 
436 /* Register memory allocation descriptor for container PTR.  ORIGIN identifies
437    type of container and GGC identifes if the allocation is handled in GGC
438    memory.  Each location is identified by file NAME, LINE in source code and
439    FUNCTION name.  */
440 
441 template <class T>
442 inline T*
register_descriptor(const void * ptr,mem_alloc_origin origin,bool ggc,const char * filename,int line,const char * function)443 mem_alloc_description<T>::register_descriptor (const void *ptr,
444 					       mem_alloc_origin origin,
445 					       bool ggc,
446 					       const char *filename,
447 					       int line,
448 					       const char *function)
449 {
450   mem_location *l = new mem_location (origin, ggc, filename, line, function);
451   return register_descriptor (ptr, l);
452 }
453 
454 /* Register instance overhead identified by PTR pointer. Allocation takes
455    SIZE bytes.  */
456 
457 template <class T>
458 inline T*
register_instance_overhead(size_t size,const void * ptr)459 mem_alloc_description<T>::register_instance_overhead (size_t size,
460 						      const void *ptr)
461 {
462   mem_usage_pair <T> *slot = m_reverse_map->get (ptr);
463   if (!slot)
464     {
465       /* Due to PCH, it can really happen.  */
466       return NULL;
467     }
468 
469   T *usage = (*slot).usage;
470   usage->register_overhead (size);
471 
472   return usage;
473 }
474 
475 /* For containers (and GGC) where we want to track every instance object,
476    we register allocation of SIZE bytes, identified by PTR pointer, belonging
477    to USAGE descriptor.  */
478 
479 template <class T>
480 void
register_object_overhead(T * usage,size_t size,const void * ptr)481 mem_alloc_description<T>::register_object_overhead (T *usage, size_t size,
482 						    const void *ptr)
483 {
484   /* In case of GGC, it is possible to have already occupied the memory
485      location.  */
486   m_reverse_object_map->put (ptr, std::pair<T *, size_t> (usage, size));
487 }
488 
489 /* Register overhead of SIZE bytes of ORIGIN type. PTR pointer is allocated
490    in NAME source file, at LINE in source code, in FUNCTION.  */
491 
492 template <class T>
493 inline T*
register_overhead(size_t size,mem_alloc_origin origin,const char * filename,int line,const char * function,const void * ptr)494 mem_alloc_description<T>::register_overhead (size_t size,
495 					     mem_alloc_origin origin,
496 					     const char *filename,
497 					     int line,
498 					     const char *function,
499 					     const void *ptr)
500 {
501   T *usage = register_descriptor (ptr, origin, filename, line, function);
502   usage->register_overhead (size);
503 
504   return usage;
505 }
506 
507 /* Release PTR pointer of SIZE bytes.  */
508 
509 template <class T>
510 inline T *
release_instance_overhead(void * ptr,size_t size,bool remove_from_map)511 mem_alloc_description<T>::release_instance_overhead (void *ptr, size_t size,
512 						     bool remove_from_map)
513 {
514   mem_usage_pair<T> *slot = m_reverse_map->get (ptr);
515 
516   if (!slot)
517     {
518       /* Due to PCH, it can really happen.  */
519       return NULL;
520     }
521 
522   T *usage = (*slot).usage;
523   usage->release_overhead (size);
524 
525   if (remove_from_map)
526     m_reverse_map->remove (ptr);
527 
528   return usage;
529 }
530 
531 /* Release instance object identified by PTR pointer.  */
532 
533 template <class T>
534 inline void
release_object_overhead(void * ptr)535 mem_alloc_description<T>::release_object_overhead (void *ptr)
536 {
537   std::pair <T *, size_t> *entry = m_reverse_object_map->get (ptr);
538   entry->first->release_overhead (entry->second);
539   m_reverse_object_map->remove (ptr);
540 }
541 
542 /* Unregister a memory allocation descriptor registered with
543    register_descriptor (remove from reverse map), unless it is
544    unregistered through release_instance_overhead with
545    REMOVE_FROM_MAP = true.  */
546 template <class T>
547 inline void
unregister_descriptor(void * ptr)548 mem_alloc_description<T>::unregister_descriptor (void *ptr)
549 {
550   m_reverse_map->remove (ptr);
551 }
552 
553 /* Default contructor.  */
554 
555 template <class T>
556 inline
mem_alloc_description()557 mem_alloc_description<T>::mem_alloc_description ()
558 {
559   m_map = new mem_map_t (13, false, false, false);
560   m_reverse_map = new reverse_mem_map_t (13, false, false, false);
561   m_reverse_object_map = new reverse_object_map_t (13, false, false, false);
562 }
563 
564 /* Default destructor.  */
565 
566 template <class T>
567 inline
~mem_alloc_description()568 mem_alloc_description<T>::~mem_alloc_description ()
569 {
570   for (typename mem_map_t::iterator it = m_map->begin (); it != m_map->end ();
571        ++it)
572     {
573       delete (*it).first;
574       delete (*it).second;
575     }
576 
577   delete m_map;
578   delete m_reverse_map;
579   delete m_reverse_object_map;
580 }
581 
582 /* Get all tracked instances registered by the description. Items are filtered
583    by ORIGIN type, LENGTH is return value where we register the number of
584    elements in the list. If we want to process custom order, CMP comparator
585    can be provided.  */
586 
587 template <class T>
588 inline
589 typename mem_alloc_description<T>::mem_list_t *
get_list(mem_alloc_origin origin,unsigned * length)590 mem_alloc_description<T>::get_list (mem_alloc_origin origin, unsigned *length)
591 {
592   /* vec data structure is not used because all vectors generate memory
593      allocation info a it would create a cycle.  */
594   size_t element_size = sizeof (mem_list_t);
595   mem_list_t *list = XCNEWVEC (mem_list_t, m_map->elements ());
596   unsigned i = 0;
597 
598   for (typename mem_map_t::iterator it = m_map->begin (); it != m_map->end ();
599        ++it)
600     if ((*it).first->m_origin == origin)
601       list[i++] = std::pair<mem_location*, T*> (*it);
602 
603   qsort (list, i, element_size, T::compare);
604   *length = i;
605 
606   return list;
607 }
608 
609 /* Get sum value for ORIGIN type of allocation for the descriptor.  */
610 
611 template <class T>
612 inline T
get_sum(mem_alloc_origin origin)613 mem_alloc_description<T>::get_sum (mem_alloc_origin origin)
614 {
615   unsigned length;
616   mem_list_t *list = get_list (origin, &length);
617   T sum;
618 
619   for (unsigned i = 0; i < length; i++)
620     sum = sum + *list[i].second;
621 
622   XDELETEVEC (list);
623 
624   return sum;
625 }
626 
627 /* Dump all tracked instances of type ORIGIN. If we want to process custom
628    order, CMP comparator can be provided.  */
629 
630 template <class T>
631 inline void
dump(mem_alloc_origin origin)632 mem_alloc_description<T>::dump (mem_alloc_origin origin)
633 {
634   unsigned length;
635 
636   fprintf (stderr, "\n");
637 
638   mem_list_t *list = get_list (origin, &length);
639   T total = get_sum (origin);
640 
641   T::print_dash_line ();
642   T::dump_header (mem_location::get_origin_name (origin));
643   T::print_dash_line ();
644   for (int i = length - 1; i >= 0; i--)
645     list[i].second->dump (list[i].first, total);
646   T::print_dash_line ();
647 
648   T::dump_header (mem_location::get_origin_name (origin));
649   T::print_dash_line ();
650   total.dump_footer ();
651   T::print_dash_line ();
652 
653   XDELETEVEC (list);
654 
655   fprintf (stderr, "\n");
656 }
657 
658 #endif // GCC_MEM_STATS_H
659