2 * sgen-gc.c: Simple generational GC.
5 * Paolo Molaro (lupus@ximian.com)
7 * Copyright 2005-2010 Novell, Inc (http://www.novell.com)
9 * Thread start/stop adapted from Boehm's GC:
10 * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
11 * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
12 * Copyright (c) 1998 by Fergus Henderson. All rights reserved.
13 * Copyright (c) 2000-2004 by Hewlett-Packard Company. All rights reserved.
15 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
16 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
18 * Permission is hereby granted to use or copy this program
19 * for any purpose, provided the above notices are retained on all copies.
20 * Permission to modify the code and to distribute modified code is granted,
21 * provided the above notices are retained, and a notice that the code was
22 * modified is included with the above copyright notice.
25 * Copyright 2001-2003 Ximian, Inc
26 * Copyright 2003-2010 Novell, Inc.
28 * Permission is hereby granted, free of charge, to any person obtaining
29 * a copy of this software and associated documentation files (the
30 * "Software"), to deal in the Software without restriction, including
31 * without limitation the rights to use, copy, modify, merge, publish,
32 * distribute, sublicense, and/or sell copies of the Software, and to
33 * permit persons to whom the Software is furnished to do so, subject to
34 * the following conditions:
36 * The above copyright notice and this permission notice shall be
37 * included in all copies or substantial portions of the Software.
39 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
40 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
42 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
44 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50 #include "utils/mono-counters.h"
51 #include "metadata/sgen-gc.h"
53 /* Pinned objects are allocated in the LOS space if bigger than half a page
54 * or from freelists otherwise. We assume that pinned objects are relatively few
55 * and they have a slow dying speed (like interned strings, thread objects).
56 * As such they will be collected only at major collections.
57 * free lists are not global: when we need memory we allocate a PinnedChunk.
58 * Each pinned chunk is made of several pages, the first of wich is used
59 * internally for bookeeping (here think of a page as 4KB). The bookeeping
60 * includes the freelists vectors and info about the object size of each page
61 * in the pinned chunk. So, when needed, a free page is found in a pinned chunk,
62 * a size is assigned to it, the page is divided in the proper chunks and each
63 * chunk is added to the freelist. To not waste space, the remaining space in the
64 * first page is used as objects of size 16 or 32 (need to measure which are more
66 * We use this same structure to allocate memory used internally by the GC, so
67 * we never use malloc/free if we need to alloc during collection: the world is stopped
68 * and malloc/free will deadlock.
69 * When we want to iterate over pinned objects, we just scan a page at a time
70 * linearly according to the size of objects in the page: the next pointer used to link
71 * the items in the freelist uses the same word as the vtable. Since we keep freelists
72 * for each pinned chunk, if the word points outside the pinned chunk it means
74 * We could avoid this expensive scanning in creative ways. We could have a policy
75 * of putting in the pinned space only objects we know about that have no struct fields
76 * with references and we can easily use a even expensive write barrier for them,
77 * since pointer writes on such objects should be rare.
78 * The best compromise is to just alloc interned strings and System.MonoType in them.
79 * It would be nice to allocate MonoThread in it, too: must check that we properly
80 * use write barriers so we don't have to do any expensive scanning of the whole pinned
81 * chunk list during minor collections. We can avoid it now because we alloc in it only
82 * reference-free objects.
84 struct _SgenPinnedChunk {
87 SgenInternalAllocator *allocator;
88 int *page_sizes; /* a 0 means the page is still unused */
90 SgenPinnedChunk *free_list_nexts [SGEN_INTERNAL_FREELIST_NUM_SLOTS];
92 void *data [1]; /* page sizes and free lists are stored here */
95 #define PINNED_FIRST_SLOT_SIZE (sizeof (gpointer) * 4)
96 #define MAX_FREELIST_SIZE 8192
98 /* This is a fixed value used for pinned chunks, not the system pagesize */
99 #define FREELIST_PAGESIZE (16*1024)
101 /* keep each size a multiple of ALLOC_ALIGN */
102 /* on 64 bit systems 8 is likely completely unused. */
103 static const int freelist_sizes [] = {
104 8, 16, 24, 32, 40, 48, 64, 80,
105 96, 128, 160, 192, 224, 256, 320, 384,
106 448, 512, 584, 680, 816, 1024, 1360, 2048,
107 2336, 2728, 3272, 4096, 5456, 8192 };
110 * Slot indexes for the fixed INTERNAL_MEM_XXX types. -1 if that type
113 static int fixed_type_freelist_slots [INTERNAL_MEM_MAX];
115 static SgenInternalAllocator unmanaged_allocator;
117 #define LARGE_INTERNAL_MEM_HEADER_MAGIC 0x7d289f3a
119 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
120 struct _LargeInternalMemHeader {
126 static long long pinned_chunk_bytes_alloced = 0;
127 static long long large_internal_bytes_alloced = 0;
129 #ifdef HEAVY_STATISTICS
130 static long long stat_internal_alloc = 0;
133 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
134 extern pthread_t main_gc_thread;
141 report_pinned_chunk (SgenPinnedChunk *chunk, int seq) {
143 int i, free_pages, num_free, free_mem;
145 for (i = 0; i < chunk->num_pages; ++i) {
146 if (!chunk->page_sizes [i])
149 printf ("Pinned chunk %d at %p, size: %d, pages: %d, free: %d\n", seq, chunk, chunk->num_pages * FREELIST_PAGESIZE, chunk->num_pages, free_pages);
150 free_mem = FREELIST_PAGESIZE * free_pages;
151 for (i = 0; i < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++i) {
152 if (!chunk->free_list [i])
155 p = chunk->free_list [i];
160 printf ("\tfree list of size %d, %d items\n", freelist_sizes [i], num_free);
161 free_mem += freelist_sizes [i] * num_free;
163 printf ("\tfree memory in chunk: %d\n", free_mem);
170 mono_sgen_report_internal_mem_usage_full (SgenInternalAllocator *alc)
172 SgenPinnedChunk *chunk;
174 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next)
175 report_pinned_chunk (chunk, i++);
179 mono_sgen_report_internal_mem_usage (void)
181 mono_sgen_report_internal_mem_usage_full (&unmanaged_allocator);
185 * Find the slot number in the freelist for memory chunks that
186 * can contain @size objects.
189 slot_for_size (size_t size)
192 /* do a binary search or lookup table later. */
193 for (slot = 0; slot < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++slot) {
194 if (freelist_sizes [slot] >= size)
197 g_assert_not_reached ();
202 mono_sgen_register_fixed_internal_mem_type (int type, size_t size)
206 g_assert (type >= 0 && type < INTERNAL_MEM_MAX);
207 g_assert (fixed_type_freelist_slots [type] == -1);
209 slot = slot_for_size (size);
210 g_assert (slot >= 0);
212 fixed_type_freelist_slots [type] = slot;
216 * Build a free list for @size memory chunks from the memory area between
217 * start_page and end_page.
220 build_freelist (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot, int size, char *start_page, char *end_page)
224 /*g_print ("building freelist for slot %d, size %d in %p\n", slot, size, chunk);*/
225 p = (void**)start_page;
226 end = (void**)(end_page - size);
227 g_assert (!chunk->free_list [slot]);
228 chunk->free_list [slot] = p;
229 while ((char*)p + size <= (char*)end) {
231 *p = (void*)((char*)p + size);
235 /*g_print ("%d items created, max: %d\n", count, (end_page - start_page) / size);*/
237 g_assert (!chunk->free_list_nexts [slot]);
238 chunk->free_list_nexts [slot] = alc->free_lists [slot];
239 alc->free_lists [slot] = chunk;
242 static SgenPinnedChunk*
243 alloc_pinned_chunk (SgenInternalAllocator *alc, gboolean managed)
245 SgenPinnedChunk *chunk;
247 int size = SGEN_PINNED_CHUNK_SIZE;
249 chunk = mono_sgen_alloc_os_memory_aligned (size, size, TRUE);
250 chunk->block.role = managed ? MEMORY_ROLE_PINNED : MEMORY_ROLE_INTERNAL;
253 mono_sgen_update_heap_boundaries ((mword)chunk, ((mword)chunk + size));
255 pinned_chunk_bytes_alloced += size;
257 /* setup the bookeeping fields */
258 chunk->num_pages = size / FREELIST_PAGESIZE;
259 offset = G_STRUCT_OFFSET (SgenPinnedChunk, data);
260 chunk->page_sizes = (void*)((char*)chunk + offset);
261 offset += sizeof (int) * chunk->num_pages;
262 offset = SGEN_ALIGN_UP (offset);
263 chunk->free_list = (void*)((char*)chunk + offset);
264 offset += sizeof (void*) * SGEN_INTERNAL_FREELIST_NUM_SLOTS;
265 offset = SGEN_ALIGN_UP (offset);
266 chunk->start_data = (void*)((char*)chunk + offset);
268 /* allocate the first page to the freelist */
269 chunk->page_sizes [0] = PINNED_FIRST_SLOT_SIZE;
270 build_freelist (alc, chunk, slot_for_size (PINNED_FIRST_SLOT_SIZE), PINNED_FIRST_SLOT_SIZE,
271 chunk->start_data, ((char*)chunk + FREELIST_PAGESIZE));
272 mono_sgen_debug_printf (4, "Allocated pinned chunk %p, size: %d\n", chunk, size);
274 chunk->block.next = alc->chunk_list;
275 alc->chunk_list = chunk;
277 chunk->allocator = alc;
282 /* Must be called with an empty freelist for the given slot. */
284 populate_chunk_page (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot)
286 int size = freelist_sizes [slot];
288 g_assert (!chunk->free_list [slot]);
289 g_assert (!chunk->free_list_nexts [slot]);
290 for (i = 0; i < chunk->num_pages; ++i) {
291 if (chunk->page_sizes [i])
293 chunk->page_sizes [i] = size;
294 build_freelist (alc, chunk, slot, size, (char*)chunk + FREELIST_PAGESIZE * i, (char*)chunk + FREELIST_PAGESIZE * (i + 1));
301 alloc_from_slot (SgenInternalAllocator *alc, int slot, int type)
303 SgenPinnedChunk *pchunk;
304 size_t size = freelist_sizes [slot];
306 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
307 if (type == INTERNAL_MEM_MS_BLOCK_INFO) {
308 g_assert (alc == &unmanaged_allocator);
311 g_assert (pthread_self () == alc->thread);
315 alc->small_internal_mem_bytes [type] += size;
317 if (alc->delayed_free_lists [slot]) {
320 p = alc->delayed_free_lists [slot];
321 } while (SGEN_CAS_PTR (&alc->delayed_free_lists [slot], *p, p) != p);
327 pchunk = alc->free_lists [slot];
329 void **p = pchunk->free_list [slot];
335 pchunk->free_list [slot] = next;
338 alc->free_lists [slot] = pchunk->free_list_nexts [slot];
339 pchunk->free_list_nexts [slot] = NULL;
346 for (pchunk = alc->chunk_list; pchunk; pchunk = pchunk->block.next) {
347 if (populate_chunk_page (alc, pchunk, slot))
351 pchunk = alloc_pinned_chunk (alc, type == INTERNAL_MEM_MANAGED);
352 /* FIXME: handle OOM */
353 if (pchunk->free_list [slot])
355 if (!populate_chunk_page (alc, pchunk, slot))
356 g_assert_not_reached ();
360 /* used for the GC-internal data structures */
362 mono_sgen_alloc_internal_full (SgenInternalAllocator *alc, size_t size, int type)
367 g_assert (fixed_type_freelist_slots [type] == -1);
369 HEAVY_STAT (++stat_internal_alloc);
371 if (size > freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
372 LargeInternalMemHeader *mh;
374 size += sizeof (LargeInternalMemHeader);
375 mh = mono_sgen_alloc_os_memory (size, TRUE);
376 mh->magic = LARGE_INTERNAL_MEM_HEADER_MAGIC;
378 /* FIXME: do a CAS here */
379 large_internal_bytes_alloced += size;
383 slot = slot_for_size (size);
384 g_assert (size <= freelist_sizes [slot]);
385 res = alloc_from_slot (alc, slot, type);
391 mono_sgen_alloc_internal_fixed (SgenInternalAllocator *allocator, int type)
393 int slot = fixed_type_freelist_slots [type];
394 g_assert (slot >= 0);
395 return alloc_from_slot (allocator, slot, type);
399 mono_sgen_alloc_internal (int type)
401 return mono_sgen_alloc_internal_fixed (&unmanaged_allocator, type);
405 mono_sgen_alloc_internal_dynamic (size_t size, int type)
407 return mono_sgen_alloc_internal_full (&unmanaged_allocator, size, type);
411 free_from_slot (SgenInternalAllocator *alc, void *addr, int slot, int type)
413 SgenPinnedChunk *pchunk = (SgenPinnedChunk*)SGEN_PINNED_CHUNK_FOR_PTR (addr);
417 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
419 g_assert (pthread_self () == alc->thread);
420 else if (main_gc_thread)
421 g_assert (pthread_self () == main_gc_thread);
424 g_assert (addr >= (void*)pchunk && (char*)addr < (char*)pchunk + pchunk->num_pages * FREELIST_PAGESIZE);
425 if (type == INTERNAL_MEM_MANAGED)
426 g_assert (pchunk->block.role == MEMORY_ROLE_PINNED);
428 g_assert (pchunk->block.role == MEMORY_ROLE_INTERNAL);
430 next = pchunk->free_list [slot];
432 pchunk->free_list [slot] = p;
435 g_assert (!pchunk->free_list_nexts [slot]);
436 pchunk->free_list_nexts [slot] = alc->free_lists [slot];
437 alc->free_lists [slot] = pchunk;
440 alc->small_internal_mem_bytes [type] -= freelist_sizes [slot];
444 mono_sgen_free_internal_full (SgenInternalAllocator *alc, void *addr, size_t size, int type)
446 LargeInternalMemHeader *mh;
448 g_assert (fixed_type_freelist_slots [type] == -1);
453 if (size <= freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
454 int slot = slot_for_size (size);
455 free_from_slot (alc, addr, slot, type);
459 mh = (LargeInternalMemHeader*)((char*)addr - G_STRUCT_OFFSET (LargeInternalMemHeader, data));
460 g_assert (mh->magic == LARGE_INTERNAL_MEM_HEADER_MAGIC);
461 g_assert (mh->size == size + sizeof (LargeInternalMemHeader));
462 /* FIXME: do a CAS */
463 large_internal_bytes_alloced -= mh->size;
464 mono_sgen_free_os_memory (mh, mh->size);
468 mono_sgen_free_internal_fixed (SgenInternalAllocator *allocator, void *addr, int type)
470 int slot = fixed_type_freelist_slots [type];
471 g_assert (slot >= 0);
473 free_from_slot (allocator, addr, slot, type);
477 mono_sgen_free_internal (void *addr, int type)
479 mono_sgen_free_internal_fixed (&unmanaged_allocator, addr, type);
483 mono_sgen_free_internal_dynamic (void *addr, size_t size, int type)
485 mono_sgen_free_internal_full (&unmanaged_allocator, addr, size, type);
489 mono_sgen_free_internal_delayed (void *addr, int type, SgenInternalAllocator *thread_allocator)
491 SgenPinnedChunk *pchunk = (SgenPinnedChunk*)SGEN_PINNED_CHUNK_FOR_PTR (addr);
492 SgenInternalAllocator *alc = pchunk->allocator;
496 if (alc == thread_allocator) {
497 mono_sgen_free_internal_fixed (alc, addr, type);
501 slot = fixed_type_freelist_slots [type];
502 g_assert (slot >= 0);
505 next = alc->delayed_free_lists [slot];
506 *(void**)addr = next;
507 } while (SGEN_CAS_PTR (&alc->delayed_free_lists [slot], addr, next) != next);
511 mono_sgen_dump_internal_mem_usage (FILE *heap_dump_file)
513 static char const *internal_mem_names [] = { "managed", "pin-queue", "fragment", "section", "scan-starts",
514 "fin-table", "finalize-entry", "dislink-table",
515 "dislink", "roots-table", "root-record", "statistics",
516 "remset", "gray-queue", "store-remset", "marksweep-tables",
517 "marksweep-block-info", "ephemeron-link", "worker-data",
522 fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%lld\"/>\n", large_internal_bytes_alloced);
523 fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%lld\"/>\n", pinned_chunk_bytes_alloced);
524 for (i = 0; i < INTERNAL_MEM_MAX; ++i) {
525 fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n",
526 internal_mem_names [i], unmanaged_allocator.small_internal_mem_bytes [i]);
531 mono_sgen_init_internal_allocator (void)
535 g_assert (SGEN_INTERNAL_FREELIST_NUM_SLOTS == sizeof (freelist_sizes) / sizeof (freelist_sizes [0]));
537 for (i = 0; i < INTERNAL_MEM_MAX; ++i)
538 fixed_type_freelist_slots [i] = -1;
540 #ifdef HEAVY_STATISTICS
541 mono_counters_register ("Internal allocs", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_internal_alloc);
545 SgenInternalAllocator*
546 mono_sgen_get_unmanaged_allocator (void)
548 return &unmanaged_allocator;
552 mono_sgen_internal_scan_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
554 SgenPinnedChunk *chunk;
559 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
560 end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
561 mono_sgen_debug_printf (6, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk);
562 for (i = 0; i < chunk->num_pages; ++i) {
563 obj_size = chunk->page_sizes [i];
566 p = i? (char*)chunk + i * FREELIST_PAGESIZE: chunk->start_data;
567 endp = i? p + FREELIST_PAGESIZE: (char*)chunk + FREELIST_PAGESIZE;
568 mono_sgen_debug_printf (6, "Page %d (size: %d, range: %p-%p)\n", i, obj_size, p, endp);
569 while (p + obj_size <= endp) {
571 /* if the first word (the vtable) is outside the chunk we have an object */
572 if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk))
573 callback ((char*)ptr, obj_size, callback_data);
581 mono_sgen_internal_update_heap_boundaries (SgenInternalAllocator *alc)
583 SgenPinnedChunk *chunk;
584 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
585 char *end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
586 mono_sgen_update_heap_boundaries ((mword)chunk, (mword)end_chunk);
591 * the array of pointers from @start to @end contains conservative
592 * pointers to objects inside @chunk: mark each referenced object
596 mark_pinned_from_addresses (SgenPinnedChunk *chunk, void **start, void **end, IterateObjectCallbackFunc callback, void *callback_data)
598 for (; start < end; start++) {
600 int offset = (char*)addr - (char*)chunk;
601 int page = offset / FREELIST_PAGESIZE;
602 int obj_offset = page == 0? offset - ((char*)chunk->start_data - (char*)chunk): offset % FREELIST_PAGESIZE;
603 int slot_size = chunk->page_sizes [page];
605 /* the page is not allocated */
608 /* would be faster if we restrict the sizes to power of two,
609 * but that's a waste of memory: need to measure. it could reduce
610 * fragmentation since there are less pages needed, if for example
611 * someone interns strings of each size we end up with one page per
612 * interned string (still this is just ~40 KB): with more fine-grained sizes
613 * this increases the number of used pages.
616 obj_offset /= slot_size;
617 obj_offset *= slot_size;
618 addr = (char*)chunk->start_data + obj_offset;
620 obj_offset /= slot_size;
621 obj_offset *= slot_size;
622 addr = (char*)chunk + page * FREELIST_PAGESIZE + obj_offset;
625 /* if the vtable is inside the chunk it's on the freelist, so skip */
626 /* FIXME: is it possible that we're pinning objects more than once here? */
627 if (*ptr && (*ptr < (void*)chunk->start_data || *ptr > (void*)((char*)chunk + chunk->num_pages * FREELIST_PAGESIZE)))
628 callback (addr, slot_size, callback_data);
633 mono_sgen_internal_scan_pinned_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
635 SgenPinnedChunk *chunk;
637 /* look for pinned addresses for pinned-alloc objects */
638 mono_sgen_debug_printf (6, "Pinning from pinned-alloc objects\n");
639 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
641 void **pinned = mono_sgen_find_optimized_pin_queue_area (chunk->start_data,
642 (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE, &num_pinned);
644 mark_pinned_from_addresses (chunk, pinned, pinned + num_pinned, callback, callback_data);