1 /* A memory statistics tracking infrastructure. 2 Copyright (C) 2015-2019 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 struct mem_location 35 { 36 /* Default constructor. */ 37 inline 38 mem_location () {} 39 40 /* Constructor. */ 41 inline 42 mem_location (mem_alloc_origin origin, bool ggc, 43 const char *filename = NULL, int line = 0, 44 const char *function = NULL): 45 m_filename (filename), m_function (function), m_line (line), m_origin 46 (origin), m_ggc (ggc) {} 47 48 /* Copy constructor. */ 49 inline 50 mem_location (mem_location &other): m_filename (other.m_filename), 51 m_function (other.m_function), m_line (other.m_line), 52 m_origin (other.m_origin), m_ggc (other.m_ggc) {} 53 54 /* Compute hash value based on file name, function name and line in 55 source code. As there is just a single pointer registered for every 56 constant that points to e.g. the same file name, we can use hash 57 of the pointer. */ 58 hashval_t 59 hash () 60 { 61 inchash::hash hash; 62 63 hash.add_ptr (m_filename); 64 hash.add_ptr (m_function); 65 hash.add_int (m_line); 66 67 return hash.end (); 68 } 69 70 /* Return true if the memory location is equal to OTHER. */ 71 int 72 equal (mem_location &other) 73 { 74 return m_filename == other.m_filename && m_function == other.m_function 75 && m_line == other.m_line; 76 } 77 78 /* Return trimmed filename for the location. */ 79 inline const char * 80 get_trimmed_filename () 81 { 82 const char *s1 = m_filename; 83 const char *s2; 84 85 while ((s2 = strstr (s1, "gcc/"))) 86 s1 = s2 + 4; 87 88 return s1; 89 } 90 91 inline char * 92 to_string () 93 { 94 unsigned l = strlen (get_trimmed_filename ()) + strlen (m_function) 95 + LOCATION_LINE_EXTRA_SPACE; 96 97 char *s = XNEWVEC (char, l); 98 sprintf (s, "%s:%i (%s)", get_trimmed_filename (), 99 m_line, m_function); 100 101 s[MIN (LOCATION_LINE_WIDTH, l - 1)] = '\0'; 102 103 return s; 104 } 105 106 /* Return display name associated to ORIGIN type. */ 107 static const char * 108 get_origin_name (mem_alloc_origin origin) 109 { 110 return mem_alloc_origin_names[(unsigned) origin]; 111 } 112 113 /* File name of source code. */ 114 const char *m_filename; 115 /* Funcation name. */ 116 const char *m_function; 117 /* Line number in source code. */ 118 int m_line; 119 /* Origin type. */ 120 mem_alloc_origin m_origin; 121 /* Flag if used by GGC allocation. */ 122 bool m_ggc; 123 }; 124 125 /* Memory usage register to a memory location. */ 126 struct mem_usage 127 { 128 /* Default constructor. */ 129 mem_usage (): m_allocated (0), m_times (0), m_peak (0), m_instances (1) {} 130 131 /* Constructor. */ 132 mem_usage (size_t allocated, size_t times, size_t peak, size_t instances = 0): 133 m_allocated (allocated), m_times (times), m_peak (peak), 134 m_instances (instances) {} 135 136 /* Register overhead of SIZE bytes. */ 137 inline void 138 register_overhead (size_t size) 139 { 140 m_allocated += size; 141 m_times++; 142 143 if (m_peak < m_allocated) 144 m_peak = m_allocated; 145 } 146 147 /* Release overhead of SIZE bytes. */ 148 inline void 149 release_overhead (size_t size) 150 { 151 gcc_assert (size <= m_allocated); 152 153 m_allocated -= size; 154 } 155 156 /* Sum the usage with SECOND usage. */ 157 mem_usage 158 operator+ (const mem_usage &second) 159 { 160 return mem_usage (m_allocated + second.m_allocated, 161 m_times + second.m_times, 162 m_peak + second.m_peak, 163 m_instances + second.m_instances); 164 } 165 166 /* Equality operator. */ 167 inline bool 168 operator== (const mem_usage &second) const 169 { 170 return (m_allocated == second.m_allocated 171 && m_peak == second.m_peak 172 && m_times == second.m_times); 173 } 174 175 /* Comparison operator. */ 176 inline bool 177 operator< (const mem_usage &second) const 178 { 179 if (*this == second) 180 return false; 181 182 return (m_allocated == second.m_allocated ? 183 (m_peak == second.m_peak ? m_times < second.m_times 184 : m_peak < second.m_peak) : m_allocated < second.m_allocated); 185 } 186 187 /* Compare wrapper used by qsort method. */ 188 static int 189 compare (const void *first, const void *second) 190 { 191 typedef std::pair<mem_location *, mem_usage *> mem_pair_t; 192 193 const mem_pair_t f = *(const mem_pair_t *)first; 194 const mem_pair_t s = *(const mem_pair_t *)second; 195 196 if (*f.second == *s.second) 197 return 0; 198 199 return *f.second < *s.second ? 1 : -1; 200 } 201 202 /* Dump usage coupled to LOC location, where TOTAL is sum of all rows. */ 203 inline void 204 dump (mem_location *loc, mem_usage &total) const 205 { 206 char *location_string = loc->to_string (); 207 208 fprintf (stderr, "%-48s " PRsa (9) ":%5.1f%%" 209 PRsa (9) PRsa (9) ":%5.1f%%%10s\n", 210 location_string, SIZE_AMOUNT (m_allocated), 211 get_percent (m_allocated, total.m_allocated), 212 SIZE_AMOUNT (m_peak), SIZE_AMOUNT (m_times), 213 get_percent (m_times, total.m_times), loc->m_ggc ? "ggc" : "heap"); 214 215 free (location_string); 216 } 217 218 /* Dump footer. */ 219 inline void 220 dump_footer () const 221 { 222 fprintf (stderr, "%s" PRsa (53) PRsa (26) "\n", "Total", 223 SIZE_AMOUNT (m_allocated), SIZE_AMOUNT (m_times)); 224 } 225 226 /* Return fraction of NOMINATOR and DENOMINATOR in percent. */ 227 static inline float 228 get_percent (size_t nominator, size_t denominator) 229 { 230 return denominator == 0 ? 0.0f : nominator * 100.0 / denominator; 231 } 232 233 /* Print line made of dashes. */ 234 static inline void 235 print_dash_line (size_t count = 140) 236 { 237 while (count--) 238 fputc ('-', stderr); 239 fputc ('\n', stderr); 240 } 241 242 /* Dump header with NAME. */ 243 static inline void 244 dump_header (const char *name) 245 { 246 fprintf (stderr, "%-48s %11s%16s%10s%17s\n", name, "Leak", "Peak", 247 "Times", "Type"); 248 } 249 250 /* Current number of allocated bytes. */ 251 size_t m_allocated; 252 /* Number of allocations. */ 253 size_t m_times; 254 /* Peak allocation in bytes. */ 255 size_t m_peak; 256 /* Number of container instances. */ 257 size_t m_instances; 258 }; 259 260 /* Memory usage pair that connectes memory usage and number 261 of allocated bytes. */ 262 template <class T> 263 struct mem_usage_pair 264 { 265 mem_usage_pair (T *usage_, size_t allocated_): usage (usage_), 266 allocated (allocated_) {} 267 268 T *usage; 269 size_t allocated; 270 }; 271 272 /* Memory allocation description. */ 273 template <class T> 274 class mem_alloc_description 275 { 276 public: 277 struct mem_location_hash : nofree_ptr_hash <mem_location> 278 { 279 static hashval_t 280 hash (value_type l) 281 { 282 inchash::hash hstate; 283 284 hstate.add_ptr ((const void *)l->m_filename); 285 hstate.add_ptr (l->m_function); 286 hstate.add_int (l->m_line); 287 288 return hstate.end (); 289 } 290 291 static bool 292 equal (value_type l1, value_type l2) 293 { 294 return (l1->m_filename == l2->m_filename 295 && l1->m_function == l2->m_function 296 && l1->m_line == l2->m_line); 297 } 298 }; 299 300 /* Internal class type definitions. */ 301 typedef hash_map <mem_location_hash, T *> mem_map_t; 302 typedef hash_map <const void *, mem_usage_pair<T> > reverse_mem_map_t; 303 typedef hash_map <const void *, std::pair<T *, size_t> > reverse_object_map_t; 304 typedef std::pair <mem_location *, T *> mem_list_t; 305 306 /* Default contructor. */ 307 mem_alloc_description (); 308 309 /* Default destructor. */ 310 ~mem_alloc_description (); 311 312 /* Returns true if instance PTR is registered by the memory description. */ 313 bool contains_descriptor_for_instance (const void *ptr); 314 315 /* Return descriptor for instance PTR. */ 316 T *get_descriptor_for_instance (const void *ptr); 317 318 /* Register memory allocation descriptor for container PTR which is 319 described by a memory LOCATION. */ 320 T *register_descriptor (const void *ptr, mem_location *location); 321 322 /* Register memory allocation descriptor for container PTR. ORIGIN identifies 323 type of container and GGC identifes if the allocation is handled in GGC 324 memory. Each location is identified by file NAME, LINE in source code and 325 FUNCTION name. */ 326 T *register_descriptor (const void *ptr, mem_alloc_origin origin, 327 bool ggc, const char *name, int line, 328 const char *function); 329 330 /* Register instance overhead identified by PTR pointer. Allocation takes 331 SIZE bytes. */ 332 T *register_instance_overhead (size_t size, const void *ptr); 333 334 /* For containers (and GGC) where we want to track every instance object, 335 we register allocation of SIZE bytes, identified by PTR pointer, belonging 336 to USAGE descriptor. */ 337 void register_object_overhead (T *usage, size_t size, const void *ptr); 338 339 /* Release PTR pointer of SIZE bytes. If REMOVE_FROM_MAP is set to true, 340 remove the instance from reverse map. Return memory usage that belongs 341 to this memory description. */ 342 T *release_instance_overhead (void *ptr, size_t size, 343 bool remove_from_map = false); 344 345 /* Release instance object identified by PTR pointer. */ 346 void release_object_overhead (void *ptr); 347 348 /* Unregister a memory allocation descriptor registered with 349 register_descriptor (remove from reverse map), unless it is 350 unregistered through release_instance_overhead with 351 REMOVE_FROM_MAP = true. */ 352 void unregister_descriptor (void *ptr); 353 354 /* Get sum value for ORIGIN type of allocation for the descriptor. */ 355 T get_sum (mem_alloc_origin origin); 356 357 /* Get all tracked instances registered by the description. Items 358 are filtered by ORIGIN type, LENGTH is return value where we register 359 the number of elements in the list. If we want to process custom order, 360 CMP comparator can be provided. */ 361 mem_list_t *get_list (mem_alloc_origin origin, unsigned *length, 362 int (*cmp) (const void *first, 363 const void *second) = NULL); 364 365 /* Dump all tracked instances of type ORIGIN. If we want to process custom 366 order, CMP comparator can be provided. */ 367 void dump (mem_alloc_origin origin, 368 int (*cmp) (const void *first, const void *second) = NULL); 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 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* 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* 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* 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* 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 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* 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 * 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 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 if (entry) 539 { 540 entry->first->release_overhead (entry->second); 541 m_reverse_object_map->remove (ptr); 542 } 543 } 544 545 /* Unregister a memory allocation descriptor registered with 546 register_descriptor (remove from reverse map), unless it is 547 unregistered through release_instance_overhead with 548 REMOVE_FROM_MAP = true. */ 549 template <class T> 550 inline void 551 mem_alloc_description<T>::unregister_descriptor (void *ptr) 552 { 553 m_reverse_map->remove (ptr); 554 } 555 556 /* Default contructor. */ 557 558 template <class T> 559 inline 560 mem_alloc_description<T>::mem_alloc_description () 561 { 562 m_map = new mem_map_t (13, false, false); 563 m_reverse_map = new reverse_mem_map_t (13, false, false); 564 m_reverse_object_map = new reverse_object_map_t (13, false, false); 565 } 566 567 /* Default destructor. */ 568 569 template <class T> 570 inline 571 mem_alloc_description<T>::~mem_alloc_description () 572 { 573 for (typename mem_map_t::iterator it = m_map->begin (); it != m_map->end (); 574 ++it) 575 { 576 delete (*it).first; 577 delete (*it).second; 578 } 579 580 delete m_map; 581 delete m_reverse_map; 582 delete m_reverse_object_map; 583 } 584 585 /* Get all tracked instances registered by the description. Items are filtered 586 by ORIGIN type, LENGTH is return value where we register the number of 587 elements in the list. If we want to process custom order, CMP comparator 588 can be provided. */ 589 590 template <class T> 591 inline 592 typename mem_alloc_description<T>::mem_list_t * 593 mem_alloc_description<T>::get_list (mem_alloc_origin origin, unsigned *length, 594 int (*cmp) (const void *first, 595 const void *second)) 596 { 597 /* vec data structure is not used because all vectors generate memory 598 allocation info a it would create a cycle. */ 599 size_t element_size = sizeof (mem_list_t); 600 mem_list_t *list = XCNEWVEC (mem_list_t, m_map->elements ()); 601 unsigned i = 0; 602 603 for (typename mem_map_t::iterator it = m_map->begin (); it != m_map->end (); 604 ++it) 605 if ((*it).first->m_origin == origin) 606 list[i++] = std::pair<mem_location*, T*> (*it); 607 608 qsort (list, i, element_size, cmp == NULL ? T::compare : cmp); 609 *length = i; 610 611 return list; 612 } 613 614 /* Get sum value for ORIGIN type of allocation for the descriptor. */ 615 616 template <class T> 617 inline T 618 mem_alloc_description<T>::get_sum (mem_alloc_origin origin) 619 { 620 unsigned length; 621 mem_list_t *list = get_list (origin, &length); 622 T sum; 623 624 for (unsigned i = 0; i < length; i++) 625 sum = sum + *list[i].second; 626 627 XDELETEVEC (list); 628 629 return sum; 630 } 631 632 /* Dump all tracked instances of type ORIGIN. If we want to process custom 633 order, CMP comparator can be provided. */ 634 635 template <class T> 636 inline void 637 mem_alloc_description<T>::dump (mem_alloc_origin origin, 638 int (*cmp) (const void *first, 639 const void *second)) 640 { 641 unsigned length; 642 643 fprintf (stderr, "\n"); 644 645 mem_list_t *list = get_list (origin, &length, cmp); 646 T total = get_sum (origin); 647 648 T::print_dash_line (); 649 T::dump_header (mem_location::get_origin_name (origin)); 650 T::print_dash_line (); 651 for (int i = length - 1; i >= 0; i--) 652 list[i].second->dump (list[i].first, total); 653 T::print_dash_line (); 654 655 T::dump_header (mem_location::get_origin_name (origin)); 656 T::print_dash_line (); 657 total.dump_footer (); 658 T::print_dash_line (); 659 660 XDELETEVEC (list); 661 662 fprintf (stderr, "\n"); 663 } 664 665 #endif // GCC_MEM_STATS_H 666