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.
48 #include "metadata/sgen-gc.h"
50 /* Pinned objects are allocated in the LOS space if bigger than half a page
51 * or from freelists otherwise. We assume that pinned objects are relatively few
52 * and they have a slow dying speed (like interned strings, thread objects).
53 * As such they will be collected only at major collections.
54 * free lists are not global: when we need memory we allocate a PinnedChunk.
55 * Each pinned chunk is made of several pages, the first of wich is used
56 * internally for bookeeping (here think of a page as 4KB). The bookeeping
57 * includes the freelists vectors and info about the object size of each page
58 * in the pinned chunk. So, when needed, a free page is found in a pinned chunk,
59 * a size is assigned to it, the page is divided in the proper chunks and each
60 * chunk is added to the freelist. To not waste space, the remaining space in the
61 * first page is used as objects of size 16 or 32 (need to measure which are more
63 * We use this same structure to allocate memory used internally by the GC, so
64 * we never use malloc/free if we need to alloc during collection: the world is stopped
65 * and malloc/free will deadlock.
66 * When we want to iterate over pinned objects, we just scan a page at a time
67 * linearly according to the size of objects in the page: the next pointer used to link
68 * the items in the freelist uses the same word as the vtable. Since we keep freelists
69 * for each pinned chunk, if the word points outside the pinned chunk it means
71 * We could avoid this expensive scanning in creative ways. We could have a policy
72 * of putting in the pinned space only objects we know about that have no struct fields
73 * with references and we can easily use a even expensive write barrier for them,
74 * since pointer writes on such objects should be rare.
75 * The best compromise is to just alloc interned strings and System.MonoType in them.
76 * It would be nice to allocate MonoThread in it, too: must check that we properly
77 * use write barriers so we don't have to do any expensive scanning of the whole pinned
78 * chunk list during minor collections. We can avoid it now because we alloc in it only
79 * reference-free objects.
81 struct _SgenPinnedChunk {
84 int *page_sizes; /* a 0 means the page is still unused */
86 SgenPinnedChunk *free_list_nexts [SGEN_INTERNAL_FREELIST_NUM_SLOTS];
88 void *data [1]; /* page sizes and free lists are stored here */
91 #define PINNED_FIRST_SLOT_SIZE (sizeof (gpointer) * 4)
92 #define MAX_FREELIST_SIZE 8192
94 /* This is a fixed value used for pinned chunks, not the system pagesize */
95 #define FREELIST_PAGESIZE (16*1024)
97 /* keep each size a multiple of ALLOC_ALIGN */
98 /* on 64 bit systems 8 is likely completely unused. */
99 static const int freelist_sizes [] = {
100 8, 16, 24, 32, 40, 48, 64, 80,
101 96, 128, 160, 192, 224, 256, 320, 384,
102 448, 512, 584, 680, 816, 1024, 1360, 2048,
103 2336, 2728, 3272, 4096, 5456, 8192 };
106 * Slot indexes for the fixed INTERNAL_MEM_XXX types. -1 if that type
109 static int fixed_type_freelist_slots [INTERNAL_MEM_MAX];
111 static SgenInternalAllocator unmanaged_allocator;
113 #define LARGE_INTERNAL_MEM_HEADER_MAGIC 0x7d289f3a
115 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
116 struct _LargeInternalMemHeader {
122 #ifdef SGEN_PARALLEL_MARK
123 static LOCK_DECLARE (internal_allocator_mutex);
124 #define LOCK_INTERNAL_ALLOCATOR pthread_mutex_lock (&internal_allocator_mutex)
125 #define UNLOCK_INTERNAL_ALLOCATOR pthread_mutex_unlock (&internal_allocator_mutex)
127 #define LOCK_INTERNAL_ALLOCATOR
128 #define UNLOCK_INTERNAL_ALLOCATOR
131 static long long pinned_chunk_bytes_alloced = 0;
132 static long long large_internal_bytes_alloced = 0;
134 #ifdef HEAVY_STATISTICS
135 static long long stat_internal_alloc = 0;
142 report_pinned_chunk (SgenPinnedChunk *chunk, int seq) {
144 int i, free_pages, num_free, free_mem;
146 for (i = 0; i < chunk->num_pages; ++i) {
147 if (!chunk->page_sizes [i])
150 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);
151 free_mem = FREELIST_PAGESIZE * free_pages;
152 for (i = 0; i < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++i) {
153 if (!chunk->free_list [i])
156 p = chunk->free_list [i];
161 printf ("\tfree list of size %d, %d items\n", freelist_sizes [i], num_free);
162 free_mem += freelist_sizes [i] * num_free;
164 printf ("\tfree memory in chunk: %d\n", free_mem);
171 mono_sgen_report_internal_mem_usage_full (SgenInternalAllocator *alc)
173 SgenPinnedChunk *chunk;
175 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next)
176 report_pinned_chunk (chunk, i++);
180 mono_sgen_report_internal_mem_usage (void)
182 mono_sgen_report_internal_mem_usage_full (&unmanaged_allocator);
186 * Find the slot number in the freelist for memory chunks that
187 * can contain @size objects.
190 slot_for_size (size_t size)
193 /* do a binary search or lookup table later. */
194 for (slot = 0; slot < SGEN_INTERNAL_FREELIST_NUM_SLOTS; ++slot) {
195 if (freelist_sizes [slot] >= size)
198 g_assert_not_reached ();
203 mono_sgen_register_fixed_internal_mem_type (int type, size_t size)
207 g_assert (type >= 0 && type < INTERNAL_MEM_MAX);
208 g_assert (fixed_type_freelist_slots [type] == -1);
210 slot = slot_for_size (size);
211 g_assert (slot >= 0);
213 fixed_type_freelist_slots [type] = slot;
217 * Build a free list for @size memory chunks from the memory area between
218 * start_page and end_page.
221 build_freelist (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot, int size, char *start_page, char *end_page)
225 /*g_print ("building freelist for slot %d, size %d in %p\n", slot, size, chunk);*/
226 p = (void**)start_page;
227 end = (void**)(end_page - size);
228 g_assert (!chunk->free_list [slot]);
229 chunk->free_list [slot] = p;
230 while ((char*)p + size <= (char*)end) {
232 *p = (void*)((char*)p + size);
236 /*g_print ("%d items created, max: %d\n", count, (end_page - start_page) / size);*/
238 g_assert (!chunk->free_list_nexts [slot]);
239 chunk->free_list_nexts [slot] = alc->free_lists [slot];
240 alc->free_lists [slot] = chunk;
243 /* LOCKING: if !managed, requires the internal allocator lock to be held */
244 static SgenPinnedChunk*
245 alloc_pinned_chunk (SgenInternalAllocator *alc, gboolean managed)
247 SgenPinnedChunk *chunk;
249 int size = SGEN_PINNED_CHUNK_SIZE;
252 LOCK_INTERNAL_ALLOCATOR;
254 chunk = mono_sgen_alloc_os_memory_aligned (size, size, TRUE);
255 chunk->block.role = managed ? MEMORY_ROLE_PINNED : MEMORY_ROLE_INTERNAL;
258 mono_sgen_update_heap_boundaries ((mword)chunk, ((mword)chunk + size));
260 pinned_chunk_bytes_alloced += size;
262 /* setup the bookeeping fields */
263 chunk->num_pages = size / FREELIST_PAGESIZE;
264 offset = G_STRUCT_OFFSET (SgenPinnedChunk, data);
265 chunk->page_sizes = (void*)((char*)chunk + offset);
266 offset += sizeof (int) * chunk->num_pages;
267 offset = SGEN_ALIGN_UP (offset);
268 chunk->free_list = (void*)((char*)chunk + offset);
269 offset += sizeof (void*) * SGEN_INTERNAL_FREELIST_NUM_SLOTS;
270 offset = SGEN_ALIGN_UP (offset);
271 chunk->start_data = (void*)((char*)chunk + offset);
273 /* allocate the first page to the freelist */
274 chunk->page_sizes [0] = PINNED_FIRST_SLOT_SIZE;
275 build_freelist (alc, chunk, slot_for_size (PINNED_FIRST_SLOT_SIZE), PINNED_FIRST_SLOT_SIZE,
276 chunk->start_data, ((char*)chunk + FREELIST_PAGESIZE));
277 mono_sgen_debug_printf (4, "Allocated pinned chunk %p, size: %d\n", chunk, size);
279 chunk->block.next = alc->chunk_list;
280 alc->chunk_list = chunk;
283 UNLOCK_INTERNAL_ALLOCATOR;
288 /* Must be called with an empty freelist for the given slot. */
290 populate_chunk_page (SgenInternalAllocator *alc, SgenPinnedChunk *chunk, int slot)
292 int size = freelist_sizes [slot];
294 g_assert (!chunk->free_list [slot]);
295 g_assert (!chunk->free_list_nexts [slot]);
296 for (i = 0; i < chunk->num_pages; ++i) {
297 if (chunk->page_sizes [i])
299 chunk->page_sizes [i] = size;
300 build_freelist (alc, chunk, slot, size, (char*)chunk + FREELIST_PAGESIZE * i, (char*)chunk + FREELIST_PAGESIZE * (i + 1));
306 /* LOCKING: assumes the internal allocator lock is held */
308 alloc_from_slot (SgenInternalAllocator *alc, int slot, int type)
310 SgenPinnedChunk *pchunk;
311 size_t size = freelist_sizes [slot];
313 alc->small_internal_mem_bytes [type] += size;
316 pchunk = alc->free_lists [slot];
318 void **p = pchunk->free_list [slot];
324 pchunk->free_list [slot] = next;
327 alc->free_lists [slot] = pchunk->free_list_nexts [slot];
328 pchunk->free_list_nexts [slot] = NULL;
335 for (pchunk = alc->chunk_list; pchunk; pchunk = pchunk->block.next) {
336 if (populate_chunk_page (alc, pchunk, slot))
340 pchunk = alloc_pinned_chunk (alc, type == INTERNAL_MEM_MANAGED);
341 /* FIXME: handle OOM */
342 if (pchunk->free_list [slot])
344 if (!populate_chunk_page (alc, pchunk, slot))
345 g_assert_not_reached ();
349 /* used for the GC-internal data structures */
351 mono_sgen_alloc_internal_full (SgenInternalAllocator *alc, size_t size, int type)
356 g_assert (fixed_type_freelist_slots [type] == -1);
358 LOCK_INTERNAL_ALLOCATOR;
360 HEAVY_STAT (++stat_internal_alloc);
362 if (size > freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
363 LargeInternalMemHeader *mh;
365 UNLOCK_INTERNAL_ALLOCATOR;
367 size += sizeof (LargeInternalMemHeader);
368 mh = mono_sgen_alloc_os_memory (size, TRUE);
369 mh->magic = LARGE_INTERNAL_MEM_HEADER_MAGIC;
371 /* FIXME: do a CAS here */
372 large_internal_bytes_alloced += size;
376 slot = slot_for_size (size);
377 g_assert (size <= freelist_sizes [slot]);
378 res = alloc_from_slot (alc, slot, type);
380 UNLOCK_INTERNAL_ALLOCATOR;
386 mono_sgen_alloc_internal (int type)
389 int slot = fixed_type_freelist_slots [type];
390 g_assert (slot >= 0);
392 LOCK_INTERNAL_ALLOCATOR;
393 res = alloc_from_slot (&unmanaged_allocator, slot, type);
394 UNLOCK_INTERNAL_ALLOCATOR;
400 mono_sgen_alloc_internal_dynamic (size_t size, int type)
402 return mono_sgen_alloc_internal_full (&unmanaged_allocator, size, type);
406 free_from_slot (SgenInternalAllocator *alc, void *addr, int slot, int type)
408 SgenPinnedChunk *pchunk = (SgenPinnedChunk*)SGEN_PINNED_CHUNK_FOR_PTR (addr);
412 LOCK_INTERNAL_ALLOCATOR;
414 g_assert (addr >= (void*)pchunk && (char*)addr < (char*)pchunk + pchunk->num_pages * FREELIST_PAGESIZE);
415 if (type == INTERNAL_MEM_MANAGED)
416 g_assert (pchunk->block.role == MEMORY_ROLE_PINNED);
418 g_assert (pchunk->block.role == MEMORY_ROLE_INTERNAL);
420 next = pchunk->free_list [slot];
422 pchunk->free_list [slot] = p;
425 g_assert (!pchunk->free_list_nexts [slot]);
426 pchunk->free_list_nexts [slot] = alc->free_lists [slot];
427 alc->free_lists [slot] = pchunk;
430 alc->small_internal_mem_bytes [type] -= freelist_sizes [slot];
432 UNLOCK_INTERNAL_ALLOCATOR;
436 mono_sgen_free_internal_full (SgenInternalAllocator *alc, void *addr, size_t size, int type)
438 LargeInternalMemHeader *mh;
440 g_assert (fixed_type_freelist_slots [type] == -1);
445 if (size <= freelist_sizes [SGEN_INTERNAL_FREELIST_NUM_SLOTS - 1]) {
446 int slot = slot_for_size (size);
447 free_from_slot (alc, addr, slot, type);
451 mh = (LargeInternalMemHeader*)((char*)addr - G_STRUCT_OFFSET (LargeInternalMemHeader, data));
452 g_assert (mh->magic == LARGE_INTERNAL_MEM_HEADER_MAGIC);
453 g_assert (mh->size == size + sizeof (LargeInternalMemHeader));
454 /* FIXME: do a CAS */
455 large_internal_bytes_alloced -= mh->size;
456 mono_sgen_free_os_memory (mh, mh->size);
460 mono_sgen_free_internal (void *addr, int type)
462 int slot = fixed_type_freelist_slots [type];
463 g_assert (slot >= 0);
465 free_from_slot (&unmanaged_allocator, addr, slot, type);
469 mono_sgen_free_internal_dynamic (void *addr, size_t size, int type)
471 mono_sgen_free_internal_full (&unmanaged_allocator, addr, size, type);
475 mono_sgen_dump_internal_mem_usage (FILE *heap_dump_file)
477 static char const *internal_mem_names [] = { "managed", "pin-queue", "fragment", "section", "scan-starts",
478 "fin-table", "finalize-entry", "dislink-table",
479 "dislink", "roots-table", "root-record", "statistics",
480 "remset", "gray-queue", "store-remset", "marksweep-tables",
481 "marksweep-block-info", "ephemeron-link" };
485 fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%lld\"/>\n", large_internal_bytes_alloced);
486 fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%lld\"/>\n", pinned_chunk_bytes_alloced);
487 for (i = 0; i < INTERNAL_MEM_MAX; ++i) {
488 fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n",
489 internal_mem_names [i], unmanaged_allocator.small_internal_mem_bytes [i]);
494 mono_sgen_init_internal_allocator (void)
498 g_assert (SGEN_INTERNAL_FREELIST_NUM_SLOTS == sizeof (freelist_sizes) / sizeof (freelist_sizes [0]));
500 for (i = 0; i < INTERNAL_MEM_MAX; ++i)
501 fixed_type_freelist_slots [i] = -1;
503 #ifdef HEAVY_STATISTICS
504 mono_counters_register ("Internal allocs", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_internal_alloc);
509 mono_sgen_internal_scan_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
511 SgenPinnedChunk *chunk;
516 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
517 end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
518 mono_sgen_debug_printf (6, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk);
519 for (i = 0; i < chunk->num_pages; ++i) {
520 obj_size = chunk->page_sizes [i];
523 p = i? (char*)chunk + i * FREELIST_PAGESIZE: chunk->start_data;
524 endp = i? p + FREELIST_PAGESIZE: (char*)chunk + FREELIST_PAGESIZE;
525 mono_sgen_debug_printf (6, "Page %d (size: %d, range: %p-%p)\n", i, obj_size, p, endp);
526 while (p + obj_size <= endp) {
528 /* if the first word (the vtable) is outside the chunk we have an object */
529 if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk))
530 callback ((char*)ptr, obj_size, callback_data);
538 * the array of pointers from @start to @end contains conservative
539 * pointers to objects inside @chunk: mark each referenced object
543 mark_pinned_from_addresses (SgenPinnedChunk *chunk, void **start, void **end, IterateObjectCallbackFunc callback, void *callback_data)
545 for (; start < end; start++) {
547 int offset = (char*)addr - (char*)chunk;
548 int page = offset / FREELIST_PAGESIZE;
549 int obj_offset = page == 0? offset - ((char*)chunk->start_data - (char*)chunk): offset % FREELIST_PAGESIZE;
550 int slot_size = chunk->page_sizes [page];
552 /* the page is not allocated */
555 /* would be faster if we restrict the sizes to power of two,
556 * but that's a waste of memory: need to measure. it could reduce
557 * fragmentation since there are less pages needed, if for example
558 * someone interns strings of each size we end up with one page per
559 * interned string (still this is just ~40 KB): with more fine-grained sizes
560 * this increases the number of used pages.
563 obj_offset /= slot_size;
564 obj_offset *= slot_size;
565 addr = (char*)chunk->start_data + obj_offset;
567 obj_offset /= slot_size;
568 obj_offset *= slot_size;
569 addr = (char*)chunk + page * FREELIST_PAGESIZE + obj_offset;
572 /* if the vtable is inside the chunk it's on the freelist, so skip */
573 /* FIXME: is it possible that we're pinning objects more than once here? */
574 if (*ptr && (*ptr < (void*)chunk->start_data || *ptr > (void*)((char*)chunk + chunk->num_pages * FREELIST_PAGESIZE)))
575 callback (addr, slot_size, callback_data);
580 mono_sgen_internal_scan_pinned_objects (SgenInternalAllocator *alc, IterateObjectCallbackFunc callback, void *callback_data)
582 SgenPinnedChunk *chunk;
584 /* look for pinned addresses for pinned-alloc objects */
585 mono_sgen_debug_printf (6, "Pinning from pinned-alloc objects\n");
586 for (chunk = alc->chunk_list; chunk; chunk = chunk->block.next) {
588 void **pinned = mono_sgen_find_optimized_pin_queue_area (chunk->start_data,
589 (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE, &num_pinned);
591 mark_pinned_from_addresses (chunk, pinned, pinned + num_pinned, callback, callback_data);