| /* "Bag-of-pages" zone garbage collector for the GNU compiler. |
| Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008 |
| Free Software Foundation, Inc. |
| |
| Contributed by Richard Henderson (rth@redhat.com) and Daniel Berlin |
| (dberlin@dberlin.org). Rewritten by Daniel Jacobowitz |
| <dan@codesourcery.com>. |
| |
| 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/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "tree.h" |
| #include "rtl.h" |
| #include "tm_p.h" |
| #include "toplev.h" |
| #include "varray.h" |
| #include "flags.h" |
| #include "ggc.h" |
| #include "timevar.h" |
| #include "params.h" |
| #include "bitmap.h" |
| |
| /* Prefer MAP_ANON(YMOUS) to /dev/zero, since we don't need to keep a |
| file open. Prefer either to valloc. */ |
| #ifdef HAVE_MMAP_ANON |
| # undef HAVE_MMAP_DEV_ZERO |
| |
| # include <sys/mman.h> |
| # ifndef MAP_FAILED |
| # define MAP_FAILED -1 |
| # endif |
| # if !defined (MAP_ANONYMOUS) && defined (MAP_ANON) |
| # define MAP_ANONYMOUS MAP_ANON |
| # endif |
| # define USING_MMAP |
| #endif |
| |
| #ifdef HAVE_MMAP_DEV_ZERO |
| # include <sys/mman.h> |
| # ifndef MAP_FAILED |
| # define MAP_FAILED -1 |
| # endif |
| # define USING_MMAP |
| #endif |
| |
| #ifndef USING_MMAP |
| #error Zone collector requires mmap |
| #endif |
| |
| #if (GCC_VERSION < 3001) |
| #define prefetch(X) ((void) X) |
| #define prefetchw(X) ((void) X) |
| #else |
| #define prefetch(X) __builtin_prefetch (X) |
| #define prefetchw(X) __builtin_prefetch (X, 1, 3) |
| #endif |
| |
| /* FUTURE NOTES: |
| |
| If we track inter-zone pointers, we can mark single zones at a |
| time. |
| |
| If we have a zone where we guarantee no inter-zone pointers, we |
| could mark that zone separately. |
| |
| The garbage zone should not be marked, and we should return 1 in |
| ggc_set_mark for any object in the garbage zone, which cuts off |
| marking quickly. */ |
| |
| /* Strategy: |
| |
| This garbage-collecting allocator segregates objects into zones. |
| It also segregates objects into "large" and "small" bins. Large |
| objects are greater than page size. |
| |
| Pages for small objects are broken up into chunks. The page has |
| a bitmap which marks the start position of each chunk (whether |
| allocated or free). Free chunks are on one of the zone's free |
| lists and contain a pointer to the next free chunk. Chunks in |
| most of the free lists have a fixed size determined by the |
| free list. Chunks in the "other" sized free list have their size |
| stored right after their chain pointer. |
| |
| Empty pages (of all sizes) are kept on a single page cache list, |
| and are considered first when new pages are required; they are |
| deallocated at the start of the next collection if they haven't |
| been recycled by then. The free page list is currently per-zone. */ |
| |
| /* Define GGC_DEBUG_LEVEL to print debugging information. |
| 0: No debugging output. |
| 1: GC statistics only. |
| 2: Page-entry allocations/deallocations as well. |
| 3: Object allocations as well. |
| 4: Object marks as well. */ |
| #define GGC_DEBUG_LEVEL (0) |
| |
| #ifndef HOST_BITS_PER_PTR |
| #define HOST_BITS_PER_PTR HOST_BITS_PER_LONG |
| #endif |
| |
| /* This structure manages small free chunks. The SIZE field is only |
| initialized if the chunk is in the "other" sized free list. Large |
| chunks are allocated one at a time to their own page, and so don't |
| come in here. */ |
| |
| struct alloc_chunk { |
| struct alloc_chunk *next_free; |
| unsigned int size; |
| }; |
| |
| /* The size of the fixed-size portion of a small page descriptor. */ |
| #define PAGE_OVERHEAD (offsetof (struct small_page_entry, alloc_bits)) |
| |
| /* The collector's idea of the page size. This must be a power of two |
| no larger than the system page size, because pages must be aligned |
| to this amount and are tracked at this granularity in the page |
| table. We choose a size at compile time for efficiency. |
| |
| We could make a better guess at compile time if PAGE_SIZE is a |
| constant in system headers, and PAGE_SHIFT is defined... */ |
| #define GGC_PAGE_SIZE 4096 |
| #define GGC_PAGE_MASK (GGC_PAGE_SIZE - 1) |
| #define GGC_PAGE_SHIFT 12 |
| |
| #if 0 |
| /* Alternative definitions which use the runtime page size. */ |
| #define GGC_PAGE_SIZE G.pagesize |
| #define GGC_PAGE_MASK G.page_mask |
| #define GGC_PAGE_SHIFT G.lg_pagesize |
| #endif |
| |
| /* The size of a small page managed by the garbage collector. This |
| must currently be GGC_PAGE_SIZE, but with a few changes could |
| be any multiple of it to reduce certain kinds of overhead. */ |
| #define SMALL_PAGE_SIZE GGC_PAGE_SIZE |
| |
| /* Free bin information. These numbers may be in need of re-tuning. |
| In general, decreasing the number of free bins would seem to |
| increase the time it takes to allocate... */ |
| |
| /* FIXME: We can't use anything but MAX_ALIGNMENT for the bin size |
| today. */ |
| |
| #define NUM_FREE_BINS 64 |
| #define FREE_BIN_DELTA MAX_ALIGNMENT |
| #define SIZE_BIN_DOWN(SIZE) ((SIZE) / FREE_BIN_DELTA) |
| |
| /* Allocation and marking parameters. */ |
| |
| /* The smallest allocatable unit to keep track of. */ |
| #define BYTES_PER_ALLOC_BIT MAX_ALIGNMENT |
| |
| /* The smallest markable unit. If we require each allocated object |
| to contain at least two allocatable units, we can use half as many |
| bits for the mark bitmap. But this adds considerable complexity |
| to sweeping. */ |
| #define BYTES_PER_MARK_BIT BYTES_PER_ALLOC_BIT |
| |
| #define BYTES_PER_MARK_WORD (8 * BYTES_PER_MARK_BIT * sizeof (mark_type)) |
| |
| /* We use this structure to determine the alignment required for |
| allocations. |
| |
| There are several things wrong with this estimation of alignment. |
| |
| The maximum alignment for a structure is often less than the |
| maximum alignment for a basic data type; for instance, on some |
| targets long long must be aligned to sizeof (int) in a structure |
| and sizeof (long long) in a variable. i386-linux is one example; |
| Darwin is another (sometimes, depending on the compiler in use). |
| |
| Also, long double is not included. Nothing in GCC uses long |
| double, so we assume that this is OK. On powerpc-darwin, adding |
| long double would bring the maximum alignment up to 16 bytes, |
| and until we need long double (or to vectorize compiler operations) |
| that's painfully wasteful. This will need to change, some day. */ |
| |
| struct max_alignment { |
| char c; |
| union { |
| HOST_WIDEST_INT i; |
| double d; |
| } u; |
| }; |
| |
| /* The biggest alignment required. */ |
| |
| #define MAX_ALIGNMENT (offsetof (struct max_alignment, u)) |
| |
| /* Compute the smallest multiple of F that is >= X. */ |
| |
| #define ROUND_UP(x, f) (CEIL (x, f) * (f)) |
| |
| /* Types to use for the allocation and mark bitmaps. It might be |
| a good idea to add ffsl to libiberty and use unsigned long |
| instead; that could speed us up where long is wider than int. */ |
| |
| typedef unsigned int alloc_type; |
| typedef unsigned int mark_type; |
| #define alloc_ffs(x) ffs(x) |
| |
| /* A page_entry records the status of an allocation page. This is the |
| common data between all three kinds of pages - small, large, and |
| PCH. */ |
| typedef struct page_entry |
| { |
| /* The address at which the memory is allocated. */ |
| char *page; |
| |
| /* The zone that this page entry belongs to. */ |
| struct alloc_zone *zone; |
| |
| #ifdef GATHER_STATISTICS |
| /* How many collections we've survived. */ |
| size_t survived; |
| #endif |
| |
| /* Does this page contain small objects, or one large object? */ |
| bool large_p; |
| |
| /* Is this page part of the loaded PCH? */ |
| bool pch_p; |
| } page_entry; |
| |
| /* Additional data needed for small pages. */ |
| struct small_page_entry |
| { |
| struct page_entry common; |
| |
| /* The next small page entry, or NULL if this is the last. */ |
| struct small_page_entry *next; |
| |
| /* If currently marking this zone, a pointer to the mark bits |
| for this page. If we aren't currently marking this zone, |
| this pointer may be stale (pointing to freed memory). */ |
| mark_type *mark_bits; |
| |
| /* The allocation bitmap. This array extends far enough to have |
| one bit for every BYTES_PER_ALLOC_BIT bytes in the page. */ |
| alloc_type alloc_bits[1]; |
| }; |
| |
| /* Additional data needed for large pages. */ |
| struct large_page_entry |
| { |
| struct page_entry common; |
| |
| /* The next large page entry, or NULL if this is the last. */ |
| struct large_page_entry *next; |
| |
| /* The number of bytes allocated, not including the page entry. */ |
| size_t bytes; |
| |
| /* The previous page in the list, so that we can unlink this one. */ |
| struct large_page_entry *prev; |
| |
| /* During marking, is this object marked? */ |
| bool mark_p; |
| }; |
| |
| /* A two-level tree is used to look up the page-entry for a given |
| pointer. Two chunks of the pointer's bits are extracted to index |
| the first and second levels of the tree, as follows: |
| |
| HOST_PAGE_SIZE_BITS |
| 32 | | |
| msb +----------------+----+------+------+ lsb |
| | | | |
| PAGE_L1_BITS | |
| | | |
| PAGE_L2_BITS |
| |
| The bottommost HOST_PAGE_SIZE_BITS are ignored, since page-entry |
| pages are aligned on system page boundaries. The next most |
| significant PAGE_L2_BITS and PAGE_L1_BITS are the second and first |
| index values in the lookup table, respectively. |
| |
| For 32-bit architectures and the settings below, there are no |
| leftover bits. For architectures with wider pointers, the lookup |
| tree points to a list of pages, which must be scanned to find the |
| correct one. */ |
| |
| #define PAGE_L1_BITS (8) |
| #define PAGE_L2_BITS (32 - PAGE_L1_BITS - GGC_PAGE_SHIFT) |
| #define PAGE_L1_SIZE ((size_t) 1 << PAGE_L1_BITS) |
| #define PAGE_L2_SIZE ((size_t) 1 << PAGE_L2_BITS) |
| |
| #define LOOKUP_L1(p) \ |
| (((size_t) (p) >> (32 - PAGE_L1_BITS)) & ((1 << PAGE_L1_BITS) - 1)) |
| |
| #define LOOKUP_L2(p) \ |
| (((size_t) (p) >> GGC_PAGE_SHIFT) & ((1 << PAGE_L2_BITS) - 1)) |
| |
| #if HOST_BITS_PER_PTR <= 32 |
| |
| /* On 32-bit hosts, we use a two level page table, as pictured above. */ |
| typedef page_entry **page_table[PAGE_L1_SIZE]; |
| |
| #else |
| |
| /* On 64-bit hosts, we use the same two level page tables plus a linked |
| list that disambiguates the top 32-bits. There will almost always be |
| exactly one entry in the list. */ |
| typedef struct page_table_chain |
| { |
| struct page_table_chain *next; |
| size_t high_bits; |
| page_entry **table[PAGE_L1_SIZE]; |
| } *page_table; |
| |
| #endif |
| |
| /* The global variables. */ |
| static struct globals |
| { |
| /* The linked list of zones. */ |
| struct alloc_zone *zones; |
| |
| /* Lookup table for associating allocation pages with object addresses. */ |
| page_table lookup; |
| |
| /* The system's page size, and related constants. */ |
| size_t pagesize; |
| size_t lg_pagesize; |
| size_t page_mask; |
| |
| /* The size to allocate for a small page entry. This includes |
| the size of the structure and the size of the allocation |
| bitmap. */ |
| size_t small_page_overhead; |
| |
| #if defined (HAVE_MMAP_DEV_ZERO) |
| /* A file descriptor open to /dev/zero for reading. */ |
| int dev_zero_fd; |
| #endif |
| |
| /* Allocate pages in chunks of this size, to throttle calls to memory |
| allocation routines. The first page is used, the rest go onto the |
| free list. */ |
| size_t quire_size; |
| |
| /* The file descriptor for debugging output. */ |
| FILE *debug_file; |
| } G; |
| |
| /* A zone allocation structure. There is one of these for every |
| distinct allocation zone. */ |
| struct alloc_zone |
| { |
| /* The most recent free chunk is saved here, instead of in the linked |
| free list, to decrease list manipulation. It is most likely that we |
| will want this one. */ |
| char *cached_free; |
| size_t cached_free_size; |
| |
| /* Linked lists of free storage. Slots 1 ... NUM_FREE_BINS have chunks of size |
| FREE_BIN_DELTA. All other chunks are in slot 0. */ |
| struct alloc_chunk *free_chunks[NUM_FREE_BINS + 1]; |
| |
| /* The highest bin index which might be non-empty. It may turn out |
| to be empty, in which case we have to search downwards. */ |
| size_t high_free_bin; |
| |
| /* Bytes currently allocated in this zone. */ |
| size_t allocated; |
| |
| /* Linked list of the small pages in this zone. */ |
| struct small_page_entry *pages; |
| |
| /* Doubly linked list of large pages in this zone. */ |
| struct large_page_entry *large_pages; |
| |
| /* If we are currently marking this zone, a pointer to the mark bits. */ |
| mark_type *mark_bits; |
| |
| /* Name of the zone. */ |
| const char *name; |
| |
| /* The number of small pages currently allocated in this zone. */ |
| size_t n_small_pages; |
| |
| /* Bytes allocated at the end of the last collection. */ |
| size_t allocated_last_gc; |
| |
| /* Total amount of memory mapped. */ |
| size_t bytes_mapped; |
| |
| /* A cache of free system pages. */ |
| struct small_page_entry *free_pages; |
| |
| /* Next zone in the linked list of zones. */ |
| struct alloc_zone *next_zone; |
| |
| /* True if this zone was collected during this collection. */ |
| bool was_collected; |
| |
| /* True if this zone should be destroyed after the next collection. */ |
| bool dead; |
| |
| #ifdef GATHER_STATISTICS |
| struct |
| { |
| /* Total memory allocated with ggc_alloc. */ |
| unsigned long long total_allocated; |
| /* Total overhead for memory to be allocated with ggc_alloc. */ |
| unsigned long long total_overhead; |
| |
| /* Total allocations and overhead for sizes less than 32, 64 and 128. |
| These sizes are interesting because they are typical cache line |
| sizes. */ |
| |
| unsigned long long total_allocated_under32; |
| unsigned long long total_overhead_under32; |
| |
| unsigned long long total_allocated_under64; |
| unsigned long long total_overhead_under64; |
| |
| unsigned long long total_allocated_under128; |
| unsigned long long total_overhead_under128; |
| } stats; |
| #endif |
| } main_zone; |
| |
| /* Some default zones. */ |
| struct alloc_zone rtl_zone; |
| struct alloc_zone tree_zone; |
| struct alloc_zone tree_id_zone; |
| |
| /* The PCH zone does not need a normal zone structure, and it does |
| not live on the linked list of zones. */ |
| struct pch_zone |
| { |
| /* The start of the PCH zone. NULL if there is none. */ |
| char *page; |
| |
| /* The end of the PCH zone. NULL if there is none. */ |
| char *end; |
| |
| /* The size of the PCH zone. 0 if there is none. */ |
| size_t bytes; |
| |
| /* The allocation bitmap for the PCH zone. */ |
| alloc_type *alloc_bits; |
| |
| /* If we are currently marking, the mark bitmap for the PCH zone. |
| When it is first read in, we could avoid marking the PCH, |
| because it will not contain any pointers to GC memory outside |
| of the PCH; however, the PCH is currently mapped as writable, |
| so we must mark it in case new pointers are added. */ |
| mark_type *mark_bits; |
| } pch_zone; |
| |
| #ifdef USING_MMAP |
| static char *alloc_anon (char *, size_t, struct alloc_zone *); |
| #endif |
| static struct small_page_entry * alloc_small_page (struct alloc_zone *); |
| static struct large_page_entry * alloc_large_page (size_t, struct alloc_zone *); |
| static void free_chunk (char *, size_t, struct alloc_zone *); |
| static void free_small_page (struct small_page_entry *); |
| static void free_large_page (struct large_page_entry *); |
| static void release_pages (struct alloc_zone *); |
| static void sweep_pages (struct alloc_zone *); |
| static bool ggc_collect_1 (struct alloc_zone *, bool); |
| static void new_ggc_zone_1 (struct alloc_zone *, const char *); |
| |
| /* Traverse the page table and find the entry for a page. |
| Die (probably) if the object wasn't allocated via GC. */ |
| |
| static inline page_entry * |
| lookup_page_table_entry (const void *p) |
| { |
| page_entry ***base; |
| size_t L1, L2; |
| |
| #if HOST_BITS_PER_PTR <= 32 |
| base = &G.lookup[0]; |
| #else |
| page_table table = G.lookup; |
| size_t high_bits = (size_t) p & ~ (size_t) 0xffffffff; |
| while (table->high_bits != high_bits) |
| table = table->next; |
| base = &table->table[0]; |
| #endif |
| |
| /* Extract the level 1 and 2 indices. */ |
| L1 = LOOKUP_L1 (p); |
| L2 = LOOKUP_L2 (p); |
| |
| return base[L1][L2]; |
| } |
| |
| /* Traverse the page table and find the entry for a page. |
| Return NULL if the object wasn't allocated via the GC. */ |
| |
| static inline page_entry * |
| lookup_page_table_if_allocated (const void *p) |
| { |
| page_entry ***base; |
| size_t L1, L2; |
| |
| #if HOST_BITS_PER_PTR <= 32 |
| base = &G.lookup[0]; |
| #else |
| page_table table = G.lookup; |
| size_t high_bits = (size_t) p & ~ (size_t) 0xffffffff; |
| while (1) |
| { |
| if (table == NULL) |
| return NULL; |
| if (table->high_bits == high_bits) |
| break; |
| table = table->next; |
| } |
| base = &table->table[0]; |
| #endif |
| |
| /* Extract the level 1 and 2 indices. */ |
| L1 = LOOKUP_L1 (p); |
| if (! base[L1]) |
| return NULL; |
| |
| L2 = LOOKUP_L2 (p); |
| if (L2 >= PAGE_L2_SIZE) |
| return NULL; |
| /* We might have a page entry which does not correspond exactly to a |
| system page. */ |
| if (base[L1][L2] && (const char *) p < base[L1][L2]->page) |
| return NULL; |
| |
| return base[L1][L2]; |
| } |
| |
| /* Set the page table entry for the page that starts at P. If ENTRY |
| is NULL, clear the entry. */ |
| |
| static void |
| set_page_table_entry (void *p, page_entry *entry) |
| { |
| page_entry ***base; |
| size_t L1, L2; |
| |
| #if HOST_BITS_PER_PTR <= 32 |
| base = &G.lookup[0]; |
| #else |
| page_table table; |
| size_t high_bits = (size_t) p & ~ (size_t) 0xffffffff; |
| for (table = G.lookup; table; table = table->next) |
| if (table->high_bits == high_bits) |
| goto found; |
| |
| /* Not found -- allocate a new table. */ |
| table = XCNEW (struct page_table_chain); |
| table->next = G.lookup; |
| table->high_bits = high_bits; |
| G.lookup = table; |
| found: |
| base = &table->table[0]; |
| #endif |
| |
| /* Extract the level 1 and 2 indices. */ |
| L1 = LOOKUP_L1 (p); |
| L2 = LOOKUP_L2 (p); |
| |
| if (base[L1] == NULL) |
| base[L1] = XCNEWVEC (page_entry *, PAGE_L2_SIZE); |
| |
| base[L1][L2] = entry; |
| } |
| |
| /* Find the page table entry associated with OBJECT. */ |
| |
| static inline struct page_entry * |
| zone_get_object_page (const void *object) |
| { |
| return lookup_page_table_entry (object); |
| } |
| |
| /* Find which element of the alloc_bits array OBJECT should be |
| recorded in. */ |
| static inline unsigned int |
| zone_get_object_alloc_word (const void *object) |
| { |
| return (((size_t) object & (GGC_PAGE_SIZE - 1)) |
| / (8 * sizeof (alloc_type) * BYTES_PER_ALLOC_BIT)); |
| } |
| |
| /* Find which bit of the appropriate word in the alloc_bits array |
| OBJECT should be recorded in. */ |
| static inline unsigned int |
| zone_get_object_alloc_bit (const void *object) |
| { |
| return (((size_t) object / BYTES_PER_ALLOC_BIT) |
| % (8 * sizeof (alloc_type))); |
| } |
| |
| /* Find which element of the mark_bits array OBJECT should be recorded |
| in. */ |
| static inline unsigned int |
| zone_get_object_mark_word (const void *object) |
| { |
| return (((size_t) object & (GGC_PAGE_SIZE - 1)) |
| / (8 * sizeof (mark_type) * BYTES_PER_MARK_BIT)); |
| } |
| |
| /* Find which bit of the appropriate word in the mark_bits array |
| OBJECT should be recorded in. */ |
| static inline unsigned int |
| zone_get_object_mark_bit (const void *object) |
| { |
| return (((size_t) object / BYTES_PER_MARK_BIT) |
| % (8 * sizeof (mark_type))); |
| } |
| |
| /* Set the allocation bit corresponding to OBJECT in its page's |
| bitmap. Used to split this object from the preceding one. */ |
| static inline void |
| zone_set_object_alloc_bit (const void *object) |
| { |
| struct small_page_entry *page |
| = (struct small_page_entry *) zone_get_object_page (object); |
| unsigned int start_word = zone_get_object_alloc_word (object); |
| unsigned int start_bit = zone_get_object_alloc_bit (object); |
| |
| page->alloc_bits[start_word] |= 1L << start_bit; |
| } |
| |
| /* Clear the allocation bit corresponding to OBJECT in PAGE's |
| bitmap. Used to coalesce this object with the preceding |
| one. */ |
| static inline void |
| zone_clear_object_alloc_bit (struct small_page_entry *page, |
| const void *object) |
| { |
| unsigned int start_word = zone_get_object_alloc_word (object); |
| unsigned int start_bit = zone_get_object_alloc_bit (object); |
| |
| /* Would xor be quicker? */ |
| page->alloc_bits[start_word] &= ~(1L << start_bit); |
| } |
| |
| /* Find the size of the object which starts at START_WORD and |
| START_BIT in ALLOC_BITS, which is at most MAX_SIZE bytes. |
| Helper function for ggc_get_size and zone_find_object_size. */ |
| |
| static inline size_t |
| zone_object_size_1 (alloc_type *alloc_bits, |
| size_t start_word, size_t start_bit, |
| size_t max_size) |
| { |
| size_t size; |
| alloc_type alloc_word; |
| int indx; |
| |
| /* Load the first word. */ |
| alloc_word = alloc_bits[start_word++]; |
| |
| /* If that was the last bit in this word, we'll want to continue |
| with the next word. Otherwise, handle the rest of this word. */ |
| if (start_bit) |
| { |
| indx = alloc_ffs (alloc_word >> start_bit); |
| if (indx) |
| /* indx is 1-based. We started at the bit after the object's |
| start, but we also ended at the bit after the object's end. |
| It cancels out. */ |
| return indx * BYTES_PER_ALLOC_BIT; |
| |
| /* The extra 1 accounts for the starting unit, before start_bit. */ |
| size = (sizeof (alloc_type) * 8 - start_bit + 1) * BYTES_PER_ALLOC_BIT; |
| |
| if (size >= max_size) |
| return max_size; |
| |
| alloc_word = alloc_bits[start_word++]; |
| } |
| else |
| size = BYTES_PER_ALLOC_BIT; |
| |
| while (alloc_word == 0) |
| { |
| size += sizeof (alloc_type) * 8 * BYTES_PER_ALLOC_BIT; |
| if (size >= max_size) |
| return max_size; |
| alloc_word = alloc_bits[start_word++]; |
| } |
| |
| indx = alloc_ffs (alloc_word); |
| return size + (indx - 1) * BYTES_PER_ALLOC_BIT; |
| } |
| |
| /* Find the size of OBJECT on small page PAGE. */ |
| |
| static inline size_t |
| zone_find_object_size (struct small_page_entry *page, |
| const void *object) |
| { |
| const char *object_midptr = (const char *) object + BYTES_PER_ALLOC_BIT; |
| unsigned int start_word = zone_get_object_alloc_word (object_midptr); |
| unsigned int start_bit = zone_get_object_alloc_bit (object_midptr); |
| size_t max_size = (page->common.page + SMALL_PAGE_SIZE |
| - (const char *) object); |
| |
| return zone_object_size_1 (page->alloc_bits, start_word, start_bit, |
| max_size); |
| } |
| |
| /* highest_bit assumes that alloc_type is 32 bits. */ |
| extern char check_alloc_type_size[(sizeof (alloc_type) == 4) ? 1 : -1]; |
| |
| /* Find the highest set bit in VALUE. Returns the bit number of that |
| bit, using the same values as ffs. */ |
| static inline alloc_type |
| highest_bit (alloc_type value) |
| { |
| /* This also assumes that alloc_type is unsigned. */ |
| value |= value >> 1; |
| value |= value >> 2; |
| value |= value >> 4; |
| value |= value >> 8; |
| value |= value >> 16; |
| value = value ^ (value >> 1); |
| return alloc_ffs (value); |
| } |
| |
| /* Find the offset from the start of an object to P, which may point |
| into the interior of the object. */ |
| |
| static unsigned long |
| zone_find_object_offset (alloc_type *alloc_bits, size_t start_word, |
| size_t start_bit) |
| { |
| unsigned int offset_in_bits; |
| alloc_type alloc_word = alloc_bits[start_word]; |
| |
| /* Mask off any bits after the initial bit, but make sure to include |
| the initial bit in the result. Note that START_BIT is |
| 0-based. */ |
| if (start_bit < 8 * sizeof (alloc_type) - 1) |
| alloc_word &= (1 << (start_bit + 1)) - 1; |
| offset_in_bits = start_bit; |
| |
| /* Search for the start of the object. */ |
| while (alloc_word == 0 && start_word > 0) |
| { |
| alloc_word = alloc_bits[--start_word]; |
| offset_in_bits += 8 * sizeof (alloc_type); |
| } |
| /* We must always find a set bit. */ |
| gcc_assert (alloc_word != 0); |
| /* Note that the result of highest_bit is 1-based. */ |
| offset_in_bits -= highest_bit (alloc_word) - 1; |
| |
| return BYTES_PER_ALLOC_BIT * offset_in_bits; |
| } |
| |
| /* Allocate the mark bits for every zone, and set the pointers on each |
| page. */ |
| static void |
| zone_allocate_marks (void) |
| { |
| struct alloc_zone *zone; |
| |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| struct small_page_entry *page; |
| mark_type *cur_marks; |
| size_t mark_words, mark_words_per_page; |
| #ifdef ENABLE_CHECKING |
| size_t n = 0; |
| #endif |
| |
| mark_words_per_page |
| = (GGC_PAGE_SIZE + BYTES_PER_MARK_WORD - 1) / BYTES_PER_MARK_WORD; |
| mark_words = zone->n_small_pages * mark_words_per_page; |
| zone->mark_bits = (mark_type *) xcalloc (sizeof (mark_type), |
| mark_words); |
| cur_marks = zone->mark_bits; |
| for (page = zone->pages; page; page = page->next) |
| { |
| page->mark_bits = cur_marks; |
| cur_marks += mark_words_per_page; |
| #ifdef ENABLE_CHECKING |
| n++; |
| #endif |
| } |
| #ifdef ENABLE_CHECKING |
| gcc_assert (n == zone->n_small_pages); |
| #endif |
| } |
| |
| /* We don't collect the PCH zone, but we do have to mark it |
| (for now). */ |
| if (pch_zone.bytes) |
| pch_zone.mark_bits |
| = (mark_type *) xcalloc (sizeof (mark_type), |
| CEIL (pch_zone.bytes, BYTES_PER_MARK_WORD)); |
| } |
| |
| /* After marking and sweeping, release the memory used for mark bits. */ |
| static void |
| zone_free_marks (void) |
| { |
| struct alloc_zone *zone; |
| |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| if (zone->mark_bits) |
| { |
| free (zone->mark_bits); |
| zone->mark_bits = NULL; |
| } |
| |
| if (pch_zone.bytes) |
| { |
| free (pch_zone.mark_bits); |
| pch_zone.mark_bits = NULL; |
| } |
| } |
| |
| #ifdef USING_MMAP |
| /* Allocate SIZE bytes of anonymous memory, preferably near PREF, |
| (if non-null). The ifdef structure here is intended to cause a |
| compile error unless exactly one of the HAVE_* is defined. */ |
| |
| static inline char * |
| alloc_anon (char *pref ATTRIBUTE_UNUSED, size_t size, struct alloc_zone *zone) |
| { |
| #ifdef HAVE_MMAP_ANON |
| char *page = (char *) mmap (pref, size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| #endif |
| #ifdef HAVE_MMAP_DEV_ZERO |
| char *page = (char *) mmap (pref, size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE, G.dev_zero_fd, 0); |
| #endif |
| |
| if (page == (char *) MAP_FAILED) |
| { |
| perror ("virtual memory exhausted"); |
| exit (FATAL_EXIT_CODE); |
| } |
| |
| /* Remember that we allocated this memory. */ |
| zone->bytes_mapped += size; |
| |
| /* Pretend we don't have access to the allocated pages. We'll enable |
| access to smaller pieces of the area in ggc_alloc. Discard the |
| handle to avoid handle leak. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS (page, size)); |
| |
| return page; |
| } |
| #endif |
| |
| /* Allocate a new page for allocating small objects in ZONE, and |
| return an entry for it. */ |
| |
| static struct small_page_entry * |
| alloc_small_page (struct alloc_zone *zone) |
| { |
| struct small_page_entry *entry; |
| |
| /* Check the list of free pages for one we can use. */ |
| entry = zone->free_pages; |
| if (entry != NULL) |
| { |
| /* Recycle the allocated memory from this page ... */ |
| zone->free_pages = entry->next; |
| } |
| else |
| { |
| /* We want just one page. Allocate a bunch of them and put the |
| extras on the freelist. (Can only do this optimization with |
| mmap for backing store.) */ |
| struct small_page_entry *e, *f = zone->free_pages; |
| int i; |
| char *page; |
| |
| page = alloc_anon (NULL, GGC_PAGE_SIZE * G.quire_size, zone); |
| |
| /* This loop counts down so that the chain will be in ascending |
| memory order. */ |
| for (i = G.quire_size - 1; i >= 1; i--) |
| { |
| e = XCNEWVAR (struct small_page_entry, G.small_page_overhead); |
| e->common.page = page + (i << GGC_PAGE_SHIFT); |
| e->common.zone = zone; |
| e->next = f; |
| f = e; |
| set_page_table_entry (e->common.page, &e->common); |
| } |
| |
| zone->free_pages = f; |
| |
| entry = XCNEWVAR (struct small_page_entry, G.small_page_overhead); |
| entry->common.page = page; |
| entry->common.zone = zone; |
| set_page_table_entry (page, &entry->common); |
| } |
| |
| zone->n_small_pages++; |
| |
| if (GGC_DEBUG_LEVEL >= 2) |
| fprintf (G.debug_file, |
| "Allocating %s page at %p, data %p-%p\n", |
| entry->common.zone->name, (PTR) entry, entry->common.page, |
| entry->common.page + SMALL_PAGE_SIZE - 1); |
| |
| return entry; |
| } |
| |
| /* Allocate a large page of size SIZE in ZONE. */ |
| |
| static struct large_page_entry * |
| alloc_large_page (size_t size, struct alloc_zone *zone) |
| { |
| struct large_page_entry *entry; |
| char *page; |
| size_t needed_size; |
| |
| needed_size = size + sizeof (struct large_page_entry); |
| page = XNEWVAR (char, needed_size); |
| |
| entry = (struct large_page_entry *) page; |
| |
| entry->next = NULL; |
| entry->common.page = page + sizeof (struct large_page_entry); |
| entry->common.large_p = true; |
| entry->common.pch_p = false; |
| entry->common.zone = zone; |
| #ifdef GATHER_STATISTICS |
| entry->common.survived = 0; |
| #endif |
| entry->mark_p = false; |
| entry->bytes = size; |
| entry->prev = NULL; |
| |
| set_page_table_entry (entry->common.page, &entry->common); |
| |
| if (GGC_DEBUG_LEVEL >= 2) |
| fprintf (G.debug_file, |
| "Allocating %s large page at %p, data %p-%p\n", |
| entry->common.zone->name, (PTR) entry, entry->common.page, |
| entry->common.page + SMALL_PAGE_SIZE - 1); |
| |
| return entry; |
| } |
| |
| |
| /* For a page that is no longer needed, put it on the free page list. */ |
| |
| static inline void |
| free_small_page (struct small_page_entry *entry) |
| { |
| if (GGC_DEBUG_LEVEL >= 2) |
| fprintf (G.debug_file, |
| "Deallocating %s page at %p, data %p-%p\n", |
| entry->common.zone->name, (PTR) entry, |
| entry->common.page, entry->common.page + SMALL_PAGE_SIZE - 1); |
| |
| gcc_assert (!entry->common.large_p); |
| |
| /* Mark the page as inaccessible. Discard the handle to |
| avoid handle leak. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS (entry->common.page, |
| SMALL_PAGE_SIZE)); |
| |
| entry->next = entry->common.zone->free_pages; |
| entry->common.zone->free_pages = entry; |
| entry->common.zone->n_small_pages--; |
| } |
| |
| /* Release a large page that is no longer needed. */ |
| |
| static inline void |
| free_large_page (struct large_page_entry *entry) |
| { |
| if (GGC_DEBUG_LEVEL >= 2) |
| fprintf (G.debug_file, |
| "Deallocating %s page at %p, data %p-%p\n", |
| entry->common.zone->name, (PTR) entry, |
| entry->common.page, entry->common.page + SMALL_PAGE_SIZE - 1); |
| |
| gcc_assert (entry->common.large_p); |
| |
| set_page_table_entry (entry->common.page, NULL); |
| free (entry); |
| } |
| |
| /* Release the free page cache to the system. */ |
| |
| static void |
| release_pages (struct alloc_zone *zone) |
| { |
| #ifdef USING_MMAP |
| struct small_page_entry *p, *next; |
| char *start; |
| size_t len; |
| |
| /* Gather up adjacent pages so they are unmapped together. */ |
| p = zone->free_pages; |
| |
| while (p) |
| { |
| start = p->common.page; |
| next = p->next; |
| len = SMALL_PAGE_SIZE; |
| set_page_table_entry (p->common.page, NULL); |
| p = next; |
| |
| while (p && p->common.page == start + len) |
| { |
| next = p->next; |
| len += SMALL_PAGE_SIZE; |
| set_page_table_entry (p->common.page, NULL); |
| p = next; |
| } |
| |
| munmap (start, len); |
| zone->bytes_mapped -= len; |
| } |
| |
| zone->free_pages = NULL; |
| #endif |
| } |
| |
| /* Place the block at PTR of size SIZE on the free list for ZONE. */ |
| |
| static inline void |
| free_chunk (char *ptr, size_t size, struct alloc_zone *zone) |
| { |
| struct alloc_chunk *chunk = (struct alloc_chunk *) ptr; |
| size_t bin = 0; |
| |
| bin = SIZE_BIN_DOWN (size); |
| gcc_assert (bin != 0); |
| if (bin > NUM_FREE_BINS) |
| { |
| bin = 0; |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (chunk, |
| sizeof (struct |
| alloc_chunk))); |
| chunk->size = size; |
| chunk->next_free = zone->free_chunks[bin]; |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS (ptr |
| + sizeof (struct |
| alloc_chunk), |
| size |
| - sizeof (struct |
| alloc_chunk))); |
| } |
| else |
| { |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (chunk, |
| sizeof (struct |
| alloc_chunk *))); |
| chunk->next_free = zone->free_chunks[bin]; |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS (ptr |
| + sizeof (struct |
| alloc_chunk *), |
| size |
| - sizeof (struct |
| alloc_chunk *))); |
| } |
| |
| zone->free_chunks[bin] = chunk; |
| if (bin > zone->high_free_bin) |
| zone->high_free_bin = bin; |
| if (GGC_DEBUG_LEVEL >= 3) |
| fprintf (G.debug_file, "Deallocating object, chunk=%p\n", (void *)chunk); |
| } |
| |
| /* Allocate a chunk of memory of at least ORIG_SIZE bytes, in ZONE. */ |
| |
| void * |
| ggc_alloc_zone_stat (size_t orig_size, struct alloc_zone *zone |
| MEM_STAT_DECL) |
| { |
| size_t bin; |
| size_t csize; |
| struct small_page_entry *entry; |
| struct alloc_chunk *chunk, **pp; |
| void *result; |
| size_t size = orig_size; |
| |
| /* Make sure that zero-sized allocations get a unique and freeable |
| pointer. */ |
| if (size == 0) |
| size = MAX_ALIGNMENT; |
| else |
| size = (size + MAX_ALIGNMENT - 1) & -MAX_ALIGNMENT; |
| |
| /* Try to allocate the object from several different sources. Each |
| of these cases is responsible for setting RESULT and SIZE to |
| describe the allocated block, before jumping to FOUND. If a |
| chunk is split, the allocate bit for the new chunk should also be |
| set. |
| |
| Large objects are handled specially. However, they'll just fail |
| the next couple of conditions, so we can wait to check for them |
| below. The large object case is relatively rare (< 1%), so this |
| is a win. */ |
| |
| /* First try to split the last chunk we allocated. For best |
| fragmentation behavior it would be better to look for a |
| free bin of the appropriate size for a small object. However, |
| we're unlikely (1% - 7%) to find one, and this gives better |
| locality behavior anyway. This case handles the lion's share |
| of all calls to this function. */ |
| if (size <= zone->cached_free_size) |
| { |
| result = zone->cached_free; |
| |
| zone->cached_free_size -= size; |
| if (zone->cached_free_size) |
| { |
| zone->cached_free += size; |
| zone_set_object_alloc_bit (zone->cached_free); |
| } |
| |
| goto found; |
| } |
| |
| /* Next, try to find a free bin of the exactly correct size. */ |
| |
| /* We want to round SIZE up, rather than down, but we know it's |
| already aligned to at least FREE_BIN_DELTA, so we can just |
| shift. */ |
| bin = SIZE_BIN_DOWN (size); |
| |
| if (bin <= NUM_FREE_BINS |
| && (chunk = zone->free_chunks[bin]) != NULL) |
| { |
| /* We have a chunk of the right size. Pull it off the free list |
| and use it. */ |
| |
| zone->free_chunks[bin] = chunk->next_free; |
| |
| /* NOTE: SIZE is only guaranteed to be right if MAX_ALIGNMENT |
| == FREE_BIN_DELTA. */ |
| result = chunk; |
| |
| /* The allocation bits are already set correctly. HIGH_FREE_BIN |
| may now be wrong, if this was the last chunk in the high bin. |
| Rather than fixing it up now, wait until we need to search |
| the free bins. */ |
| |
| goto found; |
| } |
| |
| /* Next, if there wasn't a chunk of the ideal size, look for a chunk |
| to split. We can find one in the too-big bin, or in the largest |
| sized bin with a chunk in it. Try the largest normal-sized bin |
| first. */ |
| |
| if (zone->high_free_bin > bin) |
| { |
| /* Find the highest numbered free bin. It will be at or below |
| the watermark. */ |
| while (zone->high_free_bin > bin |
| && zone->free_chunks[zone->high_free_bin] == NULL) |
| zone->high_free_bin--; |
| |
| if (zone->high_free_bin > bin) |
| { |
| size_t tbin = zone->high_free_bin; |
| chunk = zone->free_chunks[tbin]; |
| |
| /* Remove the chunk from its previous bin. */ |
| zone->free_chunks[tbin] = chunk->next_free; |
| |
| result = (char *) chunk; |
| |
| /* Save the rest of the chunk for future allocation. */ |
| if (zone->cached_free_size) |
| free_chunk (zone->cached_free, zone->cached_free_size, zone); |
| |
| chunk = (struct alloc_chunk *) ((char *) result + size); |
| zone->cached_free = (char *) chunk; |
| zone->cached_free_size = (tbin - bin) * FREE_BIN_DELTA; |
| |
| /* Mark the new free chunk as an object, so that we can |
| find the size of the newly allocated object. */ |
| zone_set_object_alloc_bit (chunk); |
| |
| /* HIGH_FREE_BIN may now be wrong, if this was the last |
| chunk in the high bin. Rather than fixing it up now, |
| wait until we need to search the free bins. */ |
| |
| goto found; |
| } |
| } |
| |
| /* Failing that, look through the "other" bucket for a chunk |
| that is large enough. */ |
| pp = &(zone->free_chunks[0]); |
| chunk = *pp; |
| while (chunk && chunk->size < size) |
| { |
| pp = &chunk->next_free; |
| chunk = *pp; |
| } |
| |
| if (chunk) |
| { |
| /* Remove the chunk from its previous bin. */ |
| *pp = chunk->next_free; |
| |
| result = (char *) chunk; |
| |
| /* Save the rest of the chunk for future allocation, if there's any |
| left over. */ |
| csize = chunk->size; |
| if (csize > size) |
| { |
| if (zone->cached_free_size) |
| free_chunk (zone->cached_free, zone->cached_free_size, zone); |
| |
| chunk = (struct alloc_chunk *) ((char *) result + size); |
| zone->cached_free = (char *) chunk; |
| zone->cached_free_size = csize - size; |
| |
| /* Mark the new free chunk as an object. */ |
| zone_set_object_alloc_bit (chunk); |
| } |
| |
| goto found; |
| } |
| |
| /* Handle large allocations. We could choose any threshold between |
| GGC_PAGE_SIZE - sizeof (struct large_page_entry) and |
| GGC_PAGE_SIZE. It can't be smaller, because then it wouldn't |
| be guaranteed to have a unique entry in the lookup table. Large |
| allocations will always fall through to here. */ |
| if (size > GGC_PAGE_SIZE) |
| { |
| struct large_page_entry *entry = alloc_large_page (size, zone); |
| |
| #ifdef GATHER_STATISTICS |
| entry->common.survived = 0; |
| #endif |
| |
| entry->next = zone->large_pages; |
| if (zone->large_pages) |
| zone->large_pages->prev = entry; |
| zone->large_pages = entry; |
| |
| result = entry->common.page; |
| |
| goto found; |
| } |
| |
| /* Failing everything above, allocate a new small page. */ |
| |
| entry = alloc_small_page (zone); |
| entry->next = zone->pages; |
| zone->pages = entry; |
| |
| /* Mark the first chunk in the new page. */ |
| entry->alloc_bits[0] = 1; |
| |
| result = entry->common.page; |
| if (size < SMALL_PAGE_SIZE) |
| { |
| if (zone->cached_free_size) |
| free_chunk (zone->cached_free, zone->cached_free_size, zone); |
| |
| zone->cached_free = (char *) result + size; |
| zone->cached_free_size = SMALL_PAGE_SIZE - size; |
| |
| /* Mark the new free chunk as an object. */ |
| zone_set_object_alloc_bit (zone->cached_free); |
| } |
| |
| found: |
| |
| /* We could save TYPE in the chunk, but we don't use that for |
| anything yet. If we wanted to, we could do it by adding it |
| either before the beginning of the chunk or after its end, |
| and adjusting the size and pointer appropriately. */ |
| |
| /* We'll probably write to this after we return. */ |
| prefetchw (result); |
| |
| #ifdef ENABLE_GC_CHECKING |
| /* `Poison' the entire allocated object. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (result, size)); |
| memset (result, 0xaf, size); |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_NOACCESS (result + orig_size, |
| size - orig_size)); |
| #endif |
| |
| /* Tell Valgrind that the memory is there, but its content isn't |
| defined. The bytes at the end of the object are still marked |
| unaccessible. */ |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (result, orig_size)); |
| |
| /* Keep track of how many bytes are being allocated. This |
| information is used in deciding when to collect. */ |
| zone->allocated += size; |
| |
| timevar_ggc_mem_total += size; |
| |
| #ifdef GATHER_STATISTICS |
| ggc_record_overhead (orig_size, size - orig_size, result PASS_MEM_STAT); |
| |
| { |
| size_t object_size = size; |
| size_t overhead = object_size - orig_size; |
| |
| zone->stats.total_overhead += overhead; |
| zone->stats.total_allocated += object_size; |
| |
| if (orig_size <= 32) |
| { |
| zone->stats.total_overhead_under32 += overhead; |
| zone->stats.total_allocated_under32 += object_size; |
| } |
| if (orig_size <= 64) |
| { |
| zone->stats.total_overhead_under64 += overhead; |
| zone->stats.total_allocated_under64 += object_size; |
| } |
| if (orig_size <= 128) |
| { |
| zone->stats.total_overhead_under128 += overhead; |
| zone->stats.total_allocated_under128 += object_size; |
| } |
| } |
| #endif |
| |
| if (GGC_DEBUG_LEVEL >= 3) |
| fprintf (G.debug_file, "Allocating object, size=%lu at %p\n", |
| (unsigned long) size, result); |
| |
| return result; |
| } |
| |
| /* Allocate a SIZE of chunk memory of GTE type, into an appropriate zone |
| for that type. */ |
| |
| void * |
| ggc_alloc_typed_stat (enum gt_types_enum gte, size_t size |
| MEM_STAT_DECL) |
| { |
| switch (gte) |
| { |
| case gt_ggc_e_14lang_tree_node: |
| return ggc_alloc_zone_pass_stat (size, &tree_zone); |
| |
| case gt_ggc_e_7rtx_def: |
| return ggc_alloc_zone_pass_stat (size, &rtl_zone); |
| |
| case gt_ggc_e_9rtvec_def: |
| return ggc_alloc_zone_pass_stat (size, &rtl_zone); |
| |
| default: |
| return ggc_alloc_zone_pass_stat (size, &main_zone); |
| } |
| } |
| |
| /* Normal ggc_alloc simply allocates into the main zone. */ |
| |
| void * |
| ggc_alloc_stat (size_t size MEM_STAT_DECL) |
| { |
| return ggc_alloc_zone_pass_stat (size, &main_zone); |
| } |
| |
| /* Poison the chunk. */ |
| #ifdef ENABLE_GC_CHECKING |
| #define poison_region(PTR, SIZE) \ |
| memset ((PTR), 0xa5, (SIZE)) |
| #else |
| #define poison_region(PTR, SIZE) |
| #endif |
| |
| /* Free the object at P. */ |
| |
| void |
| ggc_free (void *p) |
| { |
| struct page_entry *page; |
| |
| #ifdef GATHER_STATISTICS |
| ggc_free_overhead (p); |
| #endif |
| |
| poison_region (p, ggc_get_size (p)); |
| |
| page = zone_get_object_page (p); |
| |
| if (page->large_p) |
| { |
| struct large_page_entry *large_page |
| = (struct large_page_entry *) page; |
| |
| /* Remove the page from the linked list. */ |
| if (large_page->prev) |
| large_page->prev->next = large_page->next; |
| else |
| { |
| gcc_assert (large_page->common.zone->large_pages == large_page); |
| large_page->common.zone->large_pages = large_page->next; |
| } |
| if (large_page->next) |
| large_page->next->prev = large_page->prev; |
| |
| large_page->common.zone->allocated -= large_page->bytes; |
| |
| /* Release the memory associated with this object. */ |
| free_large_page (large_page); |
| } |
| else if (page->pch_p) |
| /* Don't do anything. We won't allocate a new object from the |
| PCH zone so there's no point in releasing anything. */ |
| ; |
| else |
| { |
| size_t size = ggc_get_size (p); |
| |
| page->zone->allocated -= size; |
| |
| /* Add the chunk to the free list. We don't bother with coalescing, |
| since we are likely to want a chunk of this size again. */ |
| free_chunk ((char *)p, size, page->zone); |
| } |
| } |
| |
| /* Mark function for strings. */ |
| |
| void |
| gt_ggc_m_S (const void *p) |
| { |
| page_entry *entry; |
| unsigned long offset; |
| |
| if (!p) |
| return; |
| |
| /* Look up the page on which the object is alloced. . */ |
| entry = lookup_page_table_if_allocated (p); |
| if (! entry) |
| return; |
| |
| if (entry->pch_p) |
| { |
| size_t alloc_word, alloc_bit, t; |
| t = ((const char *) p - pch_zone.page) / BYTES_PER_ALLOC_BIT; |
| alloc_word = t / (8 * sizeof (alloc_type)); |
| alloc_bit = t % (8 * sizeof (alloc_type)); |
| offset = zone_find_object_offset (pch_zone.alloc_bits, alloc_word, |
| alloc_bit); |
| } |
| else if (entry->large_p) |
| { |
| struct large_page_entry *le = (struct large_page_entry *) entry; |
| offset = ((const char *) p) - entry->page; |
| gcc_assert (offset < le->bytes); |
| } |
| else |
| { |
| struct small_page_entry *se = (struct small_page_entry *) entry; |
| unsigned int start_word = zone_get_object_alloc_word (p); |
| unsigned int start_bit = zone_get_object_alloc_bit (p); |
| offset = zone_find_object_offset (se->alloc_bits, start_word, start_bit); |
| |
| /* On some platforms a char* will not necessarily line up on an |
| allocation boundary, so we have to update the offset to |
| account for the leftover bytes. */ |
| offset += (size_t) p % BYTES_PER_ALLOC_BIT; |
| } |
| |
| if (offset) |
| { |
| /* Here we've seen a char* which does not point to the beginning |
| of an allocated object. We assume it points to the middle of |
| a STRING_CST. */ |
| gcc_assert (offset == offsetof (struct tree_string, str)); |
| p = ((const char *) p) - offset; |
| gt_ggc_mx_lang_tree_node (CONST_CAST(void *, p)); |
| return; |
| } |
| |
| /* Inefficient, but also unlikely to matter. */ |
| ggc_set_mark (p); |
| } |
| |
| /* If P is not marked, mark it and return false. Otherwise return true. |
| P must have been allocated by the GC allocator; it mustn't point to |
| static objects, stack variables, or memory allocated with malloc. */ |
| |
| int |
| ggc_set_mark (const void *p) |
| { |
| struct page_entry *page; |
| const char *ptr = (const char *) p; |
| |
| page = zone_get_object_page (p); |
| |
| if (page->pch_p) |
| { |
| size_t mark_word, mark_bit, offset; |
| offset = (ptr - pch_zone.page) / BYTES_PER_MARK_BIT; |
| mark_word = offset / (8 * sizeof (mark_type)); |
| mark_bit = offset % (8 * sizeof (mark_type)); |
| |
| if (pch_zone.mark_bits[mark_word] & (1 << mark_bit)) |
| return 1; |
| pch_zone.mark_bits[mark_word] |= (1 << mark_bit); |
| } |
| else if (page->large_p) |
| { |
| struct large_page_entry *large_page |
| = (struct large_page_entry *) page; |
| |
| if (large_page->mark_p) |
| return 1; |
| large_page->mark_p = true; |
| } |
| else |
| { |
| struct small_page_entry *small_page |
| = (struct small_page_entry *) page; |
| |
| if (small_page->mark_bits[zone_get_object_mark_word (p)] |
| & (1 << zone_get_object_mark_bit (p))) |
| return 1; |
| small_page->mark_bits[zone_get_object_mark_word (p)] |
| |= (1 << zone_get_object_mark_bit (p)); |
| } |
| |
| if (GGC_DEBUG_LEVEL >= 4) |
| fprintf (G.debug_file, "Marking %p\n", p); |
| |
| return 0; |
| } |
| |
| /* Return 1 if P has been marked, zero otherwise. |
| P must have been allocated by the GC allocator; it mustn't point to |
| static objects, stack variables, or memory allocated with malloc. */ |
| |
| int |
| ggc_marked_p (const void *p) |
| { |
| struct page_entry *page; |
| const char *ptr = (const char *) p; |
| |
| page = zone_get_object_page (p); |
| |
| if (page->pch_p) |
| { |
| size_t mark_word, mark_bit, offset; |
| offset = (ptr - pch_zone.page) / BYTES_PER_MARK_BIT; |
| mark_word = offset / (8 * sizeof (mark_type)); |
| mark_bit = offset % (8 * sizeof (mark_type)); |
| |
| return (pch_zone.mark_bits[mark_word] & (1 << mark_bit)) != 0; |
| } |
| |
| if (page->large_p) |
| { |
| struct large_page_entry *large_page |
| = (struct large_page_entry *) page; |
| |
| return large_page->mark_p; |
| } |
| else |
| { |
| struct small_page_entry *small_page |
| = (struct small_page_entry *) page; |
| |
| return 0 != (small_page->mark_bits[zone_get_object_mark_word (p)] |
| & (1 << zone_get_object_mark_bit (p))); |
| } |
| } |
| |
| /* Return the size of the gc-able object P. */ |
| |
| size_t |
| ggc_get_size (const void *p) |
| { |
| struct page_entry *page; |
| const char *ptr = (const char *) p; |
| |
| page = zone_get_object_page (p); |
| |
| if (page->pch_p) |
| { |
| size_t alloc_word, alloc_bit, offset, max_size; |
| offset = (ptr - pch_zone.page) / BYTES_PER_ALLOC_BIT + 1; |
| alloc_word = offset / (8 * sizeof (alloc_type)); |
| alloc_bit = offset % (8 * sizeof (alloc_type)); |
| max_size = pch_zone.bytes - (ptr - pch_zone.page); |
| return zone_object_size_1 (pch_zone.alloc_bits, alloc_word, alloc_bit, |
| max_size); |
| } |
| |
| if (page->large_p) |
| return ((struct large_page_entry *)page)->bytes; |
| else |
| return zone_find_object_size ((struct small_page_entry *) page, p); |
| } |
| |
| /* Initialize the ggc-zone-mmap allocator. */ |
| void |
| init_ggc (void) |
| { |
| /* The allocation size must be greater than BYTES_PER_MARK_BIT, and |
| a multiple of both BYTES_PER_ALLOC_BIT and FREE_BIN_DELTA, for |
| the current assumptions to hold. */ |
| |
| gcc_assert (FREE_BIN_DELTA == MAX_ALIGNMENT); |
| |
| /* Set up the main zone by hand. */ |
| main_zone.name = "Main zone"; |
| G.zones = &main_zone; |
| |
| /* Allocate the default zones. */ |
| new_ggc_zone_1 (&rtl_zone, "RTL zone"); |
| new_ggc_zone_1 (&tree_zone, "Tree zone"); |
| new_ggc_zone_1 (&tree_id_zone, "Tree identifier zone"); |
| |
| G.pagesize = getpagesize(); |
| G.lg_pagesize = exact_log2 (G.pagesize); |
| G.page_mask = ~(G.pagesize - 1); |
| |
| /* Require the system page size to be a multiple of GGC_PAGE_SIZE. */ |
| gcc_assert ((G.pagesize & (GGC_PAGE_SIZE - 1)) == 0); |
| |
| /* Allocate 16 system pages at a time. */ |
| G.quire_size = 16 * G.pagesize / GGC_PAGE_SIZE; |
| |
| /* Calculate the size of the allocation bitmap and other overhead. */ |
| /* Right now we allocate bits for the page header and bitmap. These |
| are wasted, but a little tricky to eliminate. */ |
| G.small_page_overhead |
| = PAGE_OVERHEAD + (GGC_PAGE_SIZE / BYTES_PER_ALLOC_BIT / 8); |
| /* G.small_page_overhead = ROUND_UP (G.small_page_overhead, MAX_ALIGNMENT); */ |
| |
| #ifdef HAVE_MMAP_DEV_ZERO |
| G.dev_zero_fd = open ("/dev/zero", O_RDONLY); |
| gcc_assert (G.dev_zero_fd != -1); |
| #endif |
| |
| #if 0 |
| G.debug_file = fopen ("ggc-mmap.debug", "w"); |
| setlinebuf (G.debug_file); |
| #else |
| G.debug_file = stdout; |
| #endif |
| |
| #ifdef USING_MMAP |
| /* StunOS has an amazing off-by-one error for the first mmap allocation |
| after fiddling with RLIMIT_STACK. The result, as hard as it is to |
| believe, is an unaligned page allocation, which would cause us to |
| hork badly if we tried to use it. */ |
| { |
| char *p = alloc_anon (NULL, G.pagesize, &main_zone); |
| struct small_page_entry *e; |
| if ((size_t)p & (G.pagesize - 1)) |
| { |
| /* How losing. Discard this one and try another. If we still |
| can't get something useful, give up. */ |
| |
| p = alloc_anon (NULL, G.pagesize, &main_zone); |
| gcc_assert (!((size_t)p & (G.pagesize - 1))); |
| } |
| |
| if (GGC_PAGE_SIZE == G.pagesize) |
| { |
| /* We have a good page, might as well hold onto it... */ |
| e = XCNEWVAR (struct small_page_entry, G.small_page_overhead); |
| e->common.page = p; |
| e->common.zone = &main_zone; |
| e->next = main_zone.free_pages; |
| set_page_table_entry (e->common.page, &e->common); |
| main_zone.free_pages = e; |
| } |
| else |
| { |
| munmap (p, G.pagesize); |
| } |
| } |
| #endif |
| } |
| |
| /* Start a new GGC zone. */ |
| |
| static void |
| new_ggc_zone_1 (struct alloc_zone *new_zone, const char * name) |
| { |
| new_zone->name = name; |
| new_zone->next_zone = G.zones->next_zone; |
| G.zones->next_zone = new_zone; |
| } |
| |
| struct alloc_zone * |
| new_ggc_zone (const char * name) |
| { |
| struct alloc_zone *new_zone = XCNEW (struct alloc_zone); |
| new_ggc_zone_1 (new_zone, name); |
| return new_zone; |
| } |
| |
| /* Destroy a GGC zone. */ |
| void |
| destroy_ggc_zone (struct alloc_zone * dead_zone) |
| { |
| struct alloc_zone *z; |
| |
| for (z = G.zones; z && z->next_zone != dead_zone; z = z->next_zone) |
| /* Just find that zone. */ |
| continue; |
| |
| /* We should have found the zone in the list. Anything else is fatal. */ |
| gcc_assert (z); |
| |
| /* z is dead, baby. z is dead. */ |
| z->dead = true; |
| } |
| |
| /* Free all empty pages and objects within a page for a given zone */ |
| |
| static void |
| sweep_pages (struct alloc_zone *zone) |
| { |
| struct large_page_entry **lpp, *lp, *lnext; |
| struct small_page_entry **spp, *sp, *snext; |
| char *last_free; |
| size_t allocated = 0; |
| bool nomarksinpage; |
| |
| /* First, reset the free_chunks lists, since we are going to |
| re-free free chunks in hopes of coalescing them into large chunks. */ |
| memset (zone->free_chunks, 0, sizeof (zone->free_chunks)); |
| zone->high_free_bin = 0; |
| zone->cached_free = NULL; |
| zone->cached_free_size = 0; |
| |
| /* Large pages are all or none affairs. Either they are completely |
| empty, or they are completely full. */ |
| lpp = &zone->large_pages; |
| for (lp = zone->large_pages; lp != NULL; lp = lnext) |
| { |
| gcc_assert (lp->common.large_p); |
| |
| lnext = lp->next; |
| |
| #ifdef GATHER_STATISTICS |
| /* This page has now survived another collection. */ |
| lp->common.survived++; |
| #endif |
| |
| if (lp->mark_p) |
| { |
| lp->mark_p = false; |
| allocated += lp->bytes; |
| lpp = &lp->next; |
| } |
| else |
| { |
| *lpp = lnext; |
| #ifdef ENABLE_GC_CHECKING |
| /* Poison the page. */ |
| memset (lp->common.page, 0xb5, SMALL_PAGE_SIZE); |
| #endif |
| if (lp->prev) |
| lp->prev->next = lp->next; |
| if (lp->next) |
| lp->next->prev = lp->prev; |
| free_large_page (lp); |
| } |
| } |
| |
| spp = &zone->pages; |
| for (sp = zone->pages; sp != NULL; sp = snext) |
| { |
| char *object, *last_object; |
| char *end; |
| alloc_type *alloc_word_p; |
| mark_type *mark_word_p; |
| |
| gcc_assert (!sp->common.large_p); |
| |
| snext = sp->next; |
| |
| #ifdef GATHER_STATISTICS |
| /* This page has now survived another collection. */ |
| sp->common.survived++; |
| #endif |
| |
| /* Step through all chunks, consolidate those that are free and |
| insert them into the free lists. Note that consolidation |
| slows down collection slightly. */ |
| |
| last_object = object = sp->common.page; |
| end = sp->common.page + SMALL_PAGE_SIZE; |
| last_free = NULL; |
| nomarksinpage = true; |
| mark_word_p = sp->mark_bits; |
| alloc_word_p = sp->alloc_bits; |
| |
| gcc_assert (BYTES_PER_ALLOC_BIT == BYTES_PER_MARK_BIT); |
| |
| object = sp->common.page; |
| do |
| { |
| unsigned int i, n; |
| alloc_type alloc_word; |
| mark_type mark_word; |
| |
| alloc_word = *alloc_word_p++; |
| mark_word = *mark_word_p++; |
| |
| if (mark_word) |
| nomarksinpage = false; |
| |
| /* There ought to be some way to do this without looping... */ |
| i = 0; |
| while ((n = alloc_ffs (alloc_word)) != 0) |
| { |
| /* Extend the current state for n - 1 bits. We can't |
| shift alloc_word by n, even though it isn't used in the |
| loop, in case only the highest bit was set. */ |
| alloc_word >>= n - 1; |
| mark_word >>= n - 1; |
| object += BYTES_PER_MARK_BIT * (n - 1); |
| |
| if (mark_word & 1) |
| { |
| if (last_free) |
| { |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (last_free, |
| object |
| - last_free)); |
| poison_region (last_free, object - last_free); |
| free_chunk (last_free, object - last_free, zone); |
| last_free = NULL; |
| } |
| else |
| allocated += object - last_object; |
| last_object = object; |
| } |
| else |
| { |
| if (last_free == NULL) |
| { |
| last_free = object; |
| allocated += object - last_object; |
| } |
| else |
| zone_clear_object_alloc_bit (sp, object); |
| } |
| |
| /* Shift to just after the alloc bit we handled. */ |
| alloc_word >>= 1; |
| mark_word >>= 1; |
| object += BYTES_PER_MARK_BIT; |
| |
| i += n; |
| } |
| |
| object += BYTES_PER_MARK_BIT * (8 * sizeof (alloc_type) - i); |
| } |
| while (object < end); |
| |
| if (nomarksinpage) |
| { |
| *spp = snext; |
| #ifdef ENABLE_GC_CHECKING |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (sp->common.page, |
| SMALL_PAGE_SIZE)); |
| /* Poison the page. */ |
| memset (sp->common.page, 0xb5, SMALL_PAGE_SIZE); |
| #endif |
| free_small_page (sp); |
| continue; |
| } |
| else if (last_free) |
| { |
| VALGRIND_DISCARD (VALGRIND_MAKE_MEM_UNDEFINED (last_free, |
| object - last_free)); |
| poison_region (last_free, object - last_free); |
| free_chunk (last_free, object - last_free, zone); |
| } |
| else |
| allocated += object - last_object; |
| |
| spp = &sp->next; |
| } |
| |
| zone->allocated = allocated; |
| } |
| |
| /* mark-and-sweep routine for collecting a single zone. NEED_MARKING |
| is true if we need to mark before sweeping, false if some other |
| zone collection has already performed marking for us. Returns true |
| if we collected, false otherwise. */ |
| |
| static bool |
| ggc_collect_1 (struct alloc_zone *zone, bool need_marking) |
| { |
| #if 0 |
| /* */ |
| { |
| int i; |
| for (i = 0; i < NUM_FREE_BINS + 1; i++) |
| { |
| struct alloc_chunk *chunk; |
| int n, tot; |
| |
| n = 0; |
| tot = 0; |
| chunk = zone->free_chunks[i]; |
| while (chunk) |
| { |
| n++; |
| tot += chunk->size; |
| chunk = chunk->next_free; |
| } |
| fprintf (stderr, "Bin %d: %d free chunks (%d bytes)\n", |
| i, n, tot); |
| } |
| } |
| /* */ |
| #endif |
| |
| if (!quiet_flag) |
| fprintf (stderr, " {%s GC %luk -> ", |
| zone->name, (unsigned long) zone->allocated / 1024); |
| |
| /* Zero the total allocated bytes. This will be recalculated in the |
| sweep phase. */ |
| zone->allocated = 0; |
| |
| /* Release the pages we freed the last time we collected, but didn't |
| reuse in the interim. */ |
| release_pages (zone); |
| |
| if (need_marking) |
| { |
| zone_allocate_marks (); |
| ggc_mark_roots (); |
| #ifdef GATHER_STATISTICS |
| ggc_prune_overhead_list (); |
| #endif |
| } |
| |
| sweep_pages (zone); |
| zone->was_collected = true; |
| zone->allocated_last_gc = zone->allocated; |
| |
| if (!quiet_flag) |
| fprintf (stderr, "%luk}", (unsigned long) zone->allocated / 1024); |
| return true; |
| } |
| |
| #ifdef GATHER_STATISTICS |
| /* Calculate the average page survival rate in terms of number of |
| collections. */ |
| |
| static float |
| calculate_average_page_survival (struct alloc_zone *zone) |
| { |
| float count = 0.0; |
| float survival = 0.0; |
| struct small_page_entry *p; |
| struct large_page_entry *lp; |
| for (p = zone->pages; p; p = p->next) |
| { |
| count += 1.0; |
| survival += p->common.survived; |
| } |
| for (lp = zone->large_pages; lp; lp = lp->next) |
| { |
| count += 1.0; |
| survival += lp->common.survived; |
| } |
| return survival/count; |
| } |
| #endif |
| |
| /* Top level collection routine. */ |
| |
| void |
| ggc_collect (void) |
| { |
| struct alloc_zone *zone; |
| bool marked = false; |
| |
| timevar_push (TV_GC); |
| |
| if (!ggc_force_collect) |
| { |
| float allocated_last_gc = 0, allocated = 0, min_expand; |
| |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| allocated_last_gc += zone->allocated_last_gc; |
| allocated += zone->allocated; |
| } |
| |
| allocated_last_gc = |
| MAX (allocated_last_gc, |
| (size_t) PARAM_VALUE (GGC_MIN_HEAPSIZE) * 1024); |
| min_expand = allocated_last_gc * PARAM_VALUE (GGC_MIN_EXPAND) / 100; |
| |
| if (allocated < allocated_last_gc + min_expand) |
| { |
| timevar_pop (TV_GC); |
| return; |
| } |
| } |
| |
| /* Start by possibly collecting the main zone. */ |
| main_zone.was_collected = false; |
| marked |= ggc_collect_1 (&main_zone, true); |
| |
| /* In order to keep the number of collections down, we don't |
| collect other zones unless we are collecting the main zone. This |
| gives us roughly the same number of collections as we used to |
| have with the old gc. The number of collection is important |
| because our main slowdown (according to profiling) is now in |
| marking. So if we mark twice as often as we used to, we'll be |
| twice as slow. Hopefully we'll avoid this cost when we mark |
| zone-at-a-time. */ |
| /* NOTE drow/2004-07-28: We now always collect the main zone, but |
| keep this code in case the heuristics are further refined. */ |
| |
| if (main_zone.was_collected) |
| { |
| struct alloc_zone *zone; |
| |
| for (zone = main_zone.next_zone; zone; zone = zone->next_zone) |
| { |
| zone->was_collected = false; |
| marked |= ggc_collect_1 (zone, !marked); |
| } |
| } |
| |
| #ifdef GATHER_STATISTICS |
| /* Print page survival stats, if someone wants them. */ |
| if (GGC_DEBUG_LEVEL >= 2) |
| { |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| if (zone->was_collected) |
| { |
| float f = calculate_average_page_survival (zone); |
| printf ("Average page survival in zone `%s' is %f\n", |
| zone->name, f); |
| } |
| } |
| } |
| #endif |
| |
| if (marked) |
| zone_free_marks (); |
| |
| /* Free dead zones. */ |
| for (zone = G.zones; zone && zone->next_zone; zone = zone->next_zone) |
| { |
| if (zone->next_zone->dead) |
| { |
| struct alloc_zone *dead_zone = zone->next_zone; |
| |
| printf ("Zone `%s' is dead and will be freed.\n", dead_zone->name); |
| |
| /* The zone must be empty. */ |
| gcc_assert (!dead_zone->allocated); |
| |
| /* Unchain the dead zone, release all its pages and free it. */ |
| zone->next_zone = zone->next_zone->next_zone; |
| release_pages (dead_zone); |
| free (dead_zone); |
| } |
| } |
| |
| timevar_pop (TV_GC); |
| } |
| |
| /* Print allocation statistics. */ |
| #define SCALE(x) ((unsigned long) ((x) < 1024*10 \ |
| ? (x) \ |
| : ((x) < 1024*1024*10 \ |
| ? (x) / 1024 \ |
| : (x) / (1024*1024)))) |
| #define LABEL(x) ((x) < 1024*10 ? ' ' : ((x) < 1024*1024*10 ? 'k' : 'M')) |
| |
| void |
| ggc_print_statistics (void) |
| { |
| struct alloc_zone *zone; |
| struct ggc_statistics stats; |
| size_t total_overhead = 0, total_allocated = 0, total_bytes_mapped = 0; |
| size_t pte_overhead, i; |
| |
| /* Clear the statistics. */ |
| memset (&stats, 0, sizeof (stats)); |
| |
| /* Make sure collection will really occur. */ |
| ggc_force_collect = true; |
| |
| /* Collect and print the statistics common across collectors. */ |
| ggc_print_common_statistics (stderr, &stats); |
| |
| ggc_force_collect = false; |
| |
| /* Release free pages so that we will not count the bytes allocated |
| there as part of the total allocated memory. */ |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| release_pages (zone); |
| |
| /* Collect some information about the various sizes of |
| allocation. */ |
| fprintf (stderr, |
| "Memory still allocated at the end of the compilation process\n"); |
| |
| fprintf (stderr, "%20s %10s %10s %10s\n", |
| "Zone", "Allocated", "Used", "Overhead"); |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| struct large_page_entry *large_page; |
| size_t overhead, allocated, in_use; |
| |
| /* Skip empty zones. */ |
| if (!zone->pages && !zone->large_pages) |
| continue; |
| |
| allocated = in_use = 0; |
| |
| overhead = sizeof (struct alloc_zone); |
| |
| for (large_page = zone->large_pages; large_page != NULL; |
| large_page = large_page->next) |
| { |
| allocated += large_page->bytes; |
| in_use += large_page->bytes; |
| overhead += sizeof (struct large_page_entry); |
| } |
| |
| /* There's no easy way to walk through the small pages finding |
| used and unused objects. Instead, add all the pages, and |
| subtract out the free list. */ |
| |
| allocated += GGC_PAGE_SIZE * zone->n_small_pages; |
| in_use += GGC_PAGE_SIZE * zone->n_small_pages; |
| overhead += G.small_page_overhead * zone->n_small_pages; |
| |
| for (i = 0; i <= NUM_FREE_BINS; i++) |
| { |
| struct alloc_chunk *chunk = zone->free_chunks[i]; |
| while (chunk) |
| { |
| in_use -= ggc_get_size (chunk); |
| chunk = chunk->next_free; |
| } |
| } |
| |
| fprintf (stderr, "%20s %10lu%c %10lu%c %10lu%c\n", |
| zone->name, |
| SCALE (allocated), LABEL (allocated), |
| SCALE (in_use), LABEL (in_use), |
| SCALE (overhead), LABEL (overhead)); |
| |
| gcc_assert (in_use == zone->allocated); |
| |
| total_overhead += overhead; |
| total_allocated += zone->allocated; |
| total_bytes_mapped += zone->bytes_mapped; |
| } |
| |
| /* Count the size of the page table as best we can. */ |
| #if HOST_BITS_PER_PTR <= 32 |
| pte_overhead = sizeof (G.lookup); |
| for (i = 0; i < PAGE_L1_SIZE; i++) |
| if (G.lookup[i]) |
| pte_overhead += PAGE_L2_SIZE * sizeof (struct page_entry *); |
| #else |
| { |
| page_table table = G.lookup; |
| pte_overhead = 0; |
| while (table) |
| { |
| pte_overhead += sizeof (*table); |
| for (i = 0; i < PAGE_L1_SIZE; i++) |
| if (table->table[i]) |
| pte_overhead += PAGE_L2_SIZE * sizeof (struct page_entry *); |
| table = table->next; |
| } |
| } |
| #endif |
| fprintf (stderr, "%20s %11s %11s %10lu%c\n", "Page Table", |
| "", "", SCALE (pte_overhead), LABEL (pte_overhead)); |
| total_overhead += pte_overhead; |
| |
| fprintf (stderr, "%20s %10lu%c %10lu%c %10lu%c\n", "Total", |
| SCALE (total_bytes_mapped), LABEL (total_bytes_mapped), |
| SCALE (total_allocated), LABEL(total_allocated), |
| SCALE (total_overhead), LABEL (total_overhead)); |
| |
| #ifdef GATHER_STATISTICS |
| { |
| unsigned long long all_overhead = 0, all_allocated = 0; |
| unsigned long long all_overhead_under32 = 0, all_allocated_under32 = 0; |
| unsigned long long all_overhead_under64 = 0, all_allocated_under64 = 0; |
| unsigned long long all_overhead_under128 = 0, all_allocated_under128 = 0; |
| |
| fprintf (stderr, "\nTotal allocations and overheads during the compilation process\n"); |
| |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| all_overhead += zone->stats.total_overhead; |
| all_allocated += zone->stats.total_allocated; |
| |
| all_allocated_under32 += zone->stats.total_allocated_under32; |
| all_overhead_under32 += zone->stats.total_overhead_under32; |
| |
| all_allocated_under64 += zone->stats.total_allocated_under64; |
| all_overhead_under64 += zone->stats.total_overhead_under64; |
| |
| all_allocated_under128 += zone->stats.total_allocated_under128; |
| all_overhead_under128 += zone->stats.total_overhead_under128; |
| |
| fprintf (stderr, "%20s: %10lld\n", |
| zone->name, zone->stats.total_allocated); |
| } |
| |
| fprintf (stderr, "\n"); |
| |
| fprintf (stderr, "Total Overhead: %10lld\n", |
| all_overhead); |
| fprintf (stderr, "Total Allocated: %10lld\n", |
| all_allocated); |
| |
| fprintf (stderr, "Total Overhead under 32B: %10lld\n", |
| all_overhead_under32); |
| fprintf (stderr, "Total Allocated under 32B: %10lld\n", |
| all_allocated_under32); |
| fprintf (stderr, "Total Overhead under 64B: %10lld\n", |
| all_overhead_under64); |
| fprintf (stderr, "Total Allocated under 64B: %10lld\n", |
| all_allocated_under64); |
| fprintf (stderr, "Total Overhead under 128B: %10lld\n", |
| all_overhead_under128); |
| fprintf (stderr, "Total Allocated under 128B: %10lld\n", |
| all_allocated_under128); |
| } |
| #endif |
| } |
| |
| /* Precompiled header support. */ |
| |
| /* For precompiled headers, we sort objects based on their type. We |
| also sort various objects into their own buckets; currently this |
| covers strings and IDENTIFIER_NODE trees. The choices of how |
| to sort buckets have not yet been tuned. */ |
| |
| #define NUM_PCH_BUCKETS (gt_types_enum_last + 3) |
| |
| #define OTHER_BUCKET (gt_types_enum_last + 0) |
| #define IDENTIFIER_BUCKET (gt_types_enum_last + 1) |
| #define STRING_BUCKET (gt_types_enum_last + 2) |
| |
| struct ggc_pch_ondisk |
| { |
| size_t total; |
| size_t type_totals[NUM_PCH_BUCKETS]; |
| }; |
| |
| struct ggc_pch_data |
| { |
| struct ggc_pch_ondisk d; |
| size_t base; |
| size_t orig_base; |
| size_t alloc_size; |
| alloc_type *alloc_bits; |
| size_t type_bases[NUM_PCH_BUCKETS]; |
| size_t start_offset; |
| }; |
| |
| /* Initialize the PCH data structure. */ |
| |
| struct ggc_pch_data * |
| init_ggc_pch (void) |
| { |
| return XCNEW (struct ggc_pch_data); |
| } |
| |
| /* Return which of the page-aligned buckets the object at X, with type |
| TYPE, should be sorted into in the PCH. Strings will have |
| IS_STRING set and TYPE will be gt_types_enum_last. Other objects |
| of unknown type will also have TYPE equal to gt_types_enum_last. */ |
| |
| static int |
| pch_bucket (void *x, enum gt_types_enum type, |
| bool is_string) |
| { |
| /* Sort identifiers into their own bucket, to improve locality |
| when searching the identifier hash table. */ |
| if (type == gt_ggc_e_14lang_tree_node |
| && TREE_CODE ((tree) x) == IDENTIFIER_NODE) |
| return IDENTIFIER_BUCKET; |
| else if (type == gt_types_enum_last) |
| { |
| if (is_string) |
| return STRING_BUCKET; |
| return OTHER_BUCKET; |
| } |
| return type; |
| } |
| |
| /* Add the size of object X to the size of the PCH data. */ |
| |
| void |
| ggc_pch_count_object (struct ggc_pch_data *d, void *x ATTRIBUTE_UNUSED, |
| size_t size, bool is_string, enum gt_types_enum type) |
| { |
| /* NOTE: Right now we don't need to align up the size of any objects. |
| Strings can be unaligned, and everything else is allocated to a |
| MAX_ALIGNMENT boundary already. */ |
| |
| d->d.type_totals[pch_bucket (x, type, is_string)] += size; |
| } |
| |
| /* Return the total size of the PCH data. */ |
| |
| size_t |
| ggc_pch_total_size (struct ggc_pch_data *d) |
| { |
| enum gt_types_enum i; |
| size_t alloc_size, total_size; |
| |
| total_size = 0; |
| for (i = 0; i < NUM_PCH_BUCKETS; i++) |
| { |
| d->d.type_totals[i] = ROUND_UP (d->d.type_totals[i], GGC_PAGE_SIZE); |
| total_size += d->d.type_totals[i]; |
| } |
| d->d.total = total_size; |
| |
| /* Include the size of the allocation bitmap. */ |
| alloc_size = CEIL (d->d.total, BYTES_PER_ALLOC_BIT * 8); |
| alloc_size = ROUND_UP (alloc_size, MAX_ALIGNMENT); |
| d->alloc_size = alloc_size; |
| |
| return d->d.total + alloc_size; |
| } |
| |
| /* Set the base address for the objects in the PCH file. */ |
| |
| void |
| ggc_pch_this_base (struct ggc_pch_data *d, void *base_) |
| { |
| int i; |
| size_t base = (size_t) base_; |
| |
| d->base = d->orig_base = base; |
| for (i = 0; i < NUM_PCH_BUCKETS; i++) |
| { |
| d->type_bases[i] = base; |
| base += d->d.type_totals[i]; |
| } |
| |
| if (d->alloc_bits == NULL) |
| d->alloc_bits = XCNEWVAR (alloc_type, d->alloc_size); |
| } |
| |
| /* Allocate a place for object X of size SIZE in the PCH file. */ |
| |
| char * |
| ggc_pch_alloc_object (struct ggc_pch_data *d, void *x, |
| size_t size, bool is_string, |
| enum gt_types_enum type) |
| { |
| size_t alloc_word, alloc_bit; |
| char *result; |
| int bucket = pch_bucket (x, type, is_string); |
| |
| /* Record the start of the object in the allocation bitmap. We |
| can't assert that the allocation bit is previously clear, because |
| strings may violate the invariant that they are at least |
| BYTES_PER_ALLOC_BIT long. This is harmless - ggc_get_size |
| should not be called for strings. */ |
| alloc_word = ((d->type_bases[bucket] - d->orig_base) |
| / (8 * sizeof (alloc_type) * BYTES_PER_ALLOC_BIT)); |
| alloc_bit = ((d->type_bases[bucket] - d->orig_base) |
| / BYTES_PER_ALLOC_BIT) % (8 * sizeof (alloc_type)); |
| d->alloc_bits[alloc_word] |= 1L << alloc_bit; |
| |
| /* Place the object at the current pointer for this bucket. */ |
| result = (char *) d->type_bases[bucket]; |
| d->type_bases[bucket] += size; |
| return result; |
| } |
| |
| /* Prepare to write out the PCH data to file F. */ |
| |
| void |
| ggc_pch_prepare_write (struct ggc_pch_data *d, |
| FILE *f) |
| { |
| /* We seek around a lot while writing. Record where the end |
| of the padding in the PCH file is, so that we can |
| locate each object's offset. */ |
| d->start_offset = ftell (f); |
| } |
| |
| /* Write out object X of SIZE to file F. */ |
| |
| void |
| ggc_pch_write_object (struct ggc_pch_data *d, |
| FILE *f, void *x, void *newx, |
| size_t size, bool is_string ATTRIBUTE_UNUSED) |
| { |
| if (fseek (f, (size_t) newx - d->orig_base + d->start_offset, SEEK_SET) != 0) |
| fatal_error ("can't seek PCH file: %m"); |
| |
| if (fwrite (x, size, 1, f) != 1) |
| fatal_error ("can't write PCH file: %m"); |
| } |
| |
| void |
| ggc_pch_finish (struct ggc_pch_data *d, FILE *f) |
| { |
| /* Write out the allocation bitmap. */ |
| if (fseek (f, d->start_offset + d->d.total, SEEK_SET) != 0) |
| fatal_error ("can't seek PCH file: %m"); |
| |
| if (fwrite (d->alloc_bits, d->alloc_size, 1, f) != 1) |
| fatal_error ("can't write PCH file: %m"); |
| |
| /* Done with the PCH, so write out our footer. */ |
| if (fwrite (&d->d, sizeof (d->d), 1, f) != 1) |
| fatal_error ("can't write PCH file: %m"); |
| |
| free (d->alloc_bits); |
| free (d); |
| } |
| |
| /* The PCH file from F has been mapped at ADDR. Read in any |
| additional data from the file and set up the GC state. */ |
| |
| void |
| ggc_pch_read (FILE *f, void *addr) |
| { |
| struct ggc_pch_ondisk d; |
| size_t alloc_size; |
| struct alloc_zone *zone; |
| struct page_entry *pch_page; |
| char *p; |
| |
| if (fread (&d, sizeof (d), 1, f) != 1) |
| fatal_error ("can't read PCH file: %m"); |
| |
| alloc_size = CEIL (d.total, BYTES_PER_ALLOC_BIT * 8); |
| alloc_size = ROUND_UP (alloc_size, MAX_ALIGNMENT); |
| |
| pch_zone.bytes = d.total; |
| pch_zone.alloc_bits = (alloc_type *) ((char *) addr + pch_zone.bytes); |
| pch_zone.page = (char *) addr; |
| pch_zone.end = (char *) pch_zone.alloc_bits; |
| |
| /* We've just read in a PCH file. So, every object that used to be |
| allocated is now free. */ |
| for (zone = G.zones; zone; zone = zone->next_zone) |
| { |
| struct small_page_entry *page, *next_page; |
| struct large_page_entry *large_page, *next_large_page; |
| |
| zone->allocated = 0; |
| |
| /* Clear the zone's free chunk list. */ |
| memset (zone->free_chunks, 0, sizeof (zone->free_chunks)); |
| zone->high_free_bin = 0; |
| zone->cached_free = NULL; |
| zone->cached_free_size = 0; |
| |
| /* Move all the small pages onto the free list. */ |
| for (page = zone->pages; page != NULL; page = next_page) |
| { |
| next_page = page->next; |
| memset (page->alloc_bits, 0, |
| G.small_page_overhead - PAGE_OVERHEAD); |
| free_small_page (page); |
| } |
| |
| /* Discard all the large pages. */ |
| for (large_page = zone->large_pages; large_page != NULL; |
| large_page = next_large_page) |
| { |
| next_large_page = large_page->next; |
| free_large_page (large_page); |
| } |
| |
| zone->pages = NULL; |
| zone->large_pages = NULL; |
| } |
| |
| /* Allocate the dummy page entry for the PCH, and set all pages |
| mapped into the PCH to reference it. */ |
| pch_page = XCNEW (struct page_entry); |
| pch_page->page = pch_zone.page; |
| pch_page->pch_p = true; |
| |
| for (p = pch_zone.page; p < pch_zone.end; p += GGC_PAGE_SIZE) |
| set_page_table_entry (p, pch_page); |
| } |