[WinForms] Fix #18506 ActiveTracker, do not propagate message to control when it...
[mono-project.git] / mono / sgen / sgen-gc.c
blob03b2b9ec03468915ca97922e529ed125e4d23f0d
1 /**
2 * \file
3 * Simple generational GC.
5 * Author:
6 * Paolo Molaro (lupus@ximian.com)
7 * Rodrigo Kumpera (kumpera@gmail.com)
9 * Copyright 2005-2011 Novell, Inc (http://www.novell.com)
10 * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
12 * Thread start/stop adapted from Boehm's GC:
13 * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
14 * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
15 * Copyright (c) 1998 by Fergus Henderson. All rights reserved.
16 * Copyright (c) 2000-2004 by Hewlett-Packard Company. All rights reserved.
17 * Copyright 2001-2003 Ximian, Inc
18 * Copyright 2003-2010 Novell, Inc.
19 * Copyright 2011 Xamarin, Inc.
20 * Copyright (C) 2012 Xamarin Inc
22 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
24 * Important: allocation provides always zeroed memory, having to do
25 * a memset after allocation is deadly for performance.
26 * Memory usage at startup is currently as follows:
27 * 64 KB pinned space
28 * 64 KB internal space
29 * size of nursery
30 * We should provide a small memory config with half the sizes
32 * We currently try to make as few mono assumptions as possible:
33 * 1) 2-word header with no GC pointers in it (first vtable, second to store the
34 * forwarding ptr)
35 * 2) gc descriptor is the second word in the vtable (first word in the class)
36 * 3) 8 byte alignment is the minimum and enough (not true for special structures (SIMD), FIXME)
37 * 4) there is a function to get an object's size and the number of
38 * elements in an array.
39 * 5) we know the special way bounds are allocated for complex arrays
40 * 6) we know about proxies and how to treat them when domains are unloaded
42 * Always try to keep stack usage to a minimum: no recursive behaviour
43 * and no large stack allocs.
45 * General description.
46 * Objects are initially allocated in a nursery using a fast bump-pointer technique.
47 * When the nursery is full we start a nursery collection: this is performed with a
48 * copying GC.
49 * When the old generation is full we start a copying GC of the old generation as well:
50 * this will be changed to mark&sweep with copying when fragmentation becomes to severe
51 * in the future. Maybe we'll even do both during the same collection like IMMIX.
53 * The things that complicate this description are:
54 * *) pinned objects: we can't move them so we need to keep track of them
55 * *) no precise info of the thread stacks and registers: we need to be able to
56 * quickly find the objects that may be referenced conservatively and pin them
57 * (this makes the first issues more important)
58 * *) large objects are too expensive to be dealt with using copying GC: we handle them
59 * with mark/sweep during major collections
60 * *) some objects need to not move even if they are small (interned strings, Type handles):
61 * we use mark/sweep for them, too: they are not allocated in the nursery, but inside
62 * PinnedChunks regions
66 * TODO:
68 *) we could have a function pointer in MonoClass to implement
69 customized write barriers for value types
71 *) investigate the stuff needed to advance a thread to a GC-safe
72 point (single-stepping, read from unmapped memory etc) and implement it.
73 This would enable us to inline allocations and write barriers, for example,
74 or at least parts of them, like the write barrier checks.
75 We may need this also for handling precise info on stacks, even simple things
76 as having uninitialized data on the stack and having to wait for the prolog
77 to zero it. Not an issue for the last frame that we scan conservatively.
78 We could always not trust the value in the slots anyway.
80 *) modify the jit to save info about references in stack locations:
81 this can be done just for locals as a start, so that at least
82 part of the stack is handled precisely.
84 *) test/fix endianess issues
86 *) Implement a card table as the write barrier instead of remembered
87 sets? Card tables are not easy to implement with our current
88 memory layout. We have several different kinds of major heap
89 objects: Small objects in regular blocks, small objects in pinned
90 chunks and LOS objects. If we just have a pointer we have no way
91 to tell which kind of object it points into, therefore we cannot
92 know where its card table is. The least we have to do to make
93 this happen is to get rid of write barriers for indirect stores.
94 (See next item)
96 *) Get rid of write barriers for indirect stores. We can do this by
97 telling the GC to wbarrier-register an object once we do an ldloca
98 or ldelema on it, and to unregister it once it's not used anymore
99 (it can only travel downwards on the stack). The problem with
100 unregistering is that it needs to happen eventually no matter
101 what, even if exceptions are thrown, the thread aborts, etc.
102 Rodrigo suggested that we could do only the registering part and
103 let the collector find out (pessimistically) when it's safe to
104 unregister, namely when the stack pointer of the thread that
105 registered the object is higher than it was when the registering
106 happened. This might make for a good first implementation to get
107 some data on performance.
109 *) Some sort of blacklist support? Blacklists is a concept from the
110 Boehm GC: if during a conservative scan we find pointers to an
111 area which we might use as heap, we mark that area as unusable, so
112 pointer retention by random pinning pointers is reduced.
114 *) experiment with max small object size (very small right now - 2kb,
115 because it's tied to the max freelist size)
117 *) add an option to mmap the whole heap in one chunk: it makes for many
118 simplifications in the checks (put the nursery at the top and just use a single
119 check for inclusion/exclusion): the issue this has is that on 32 bit systems it's
120 not flexible (too much of the address space may be used by default or we can't
121 increase the heap as needed) and we'd need a race-free mechanism to return memory
122 back to the system (mprotect(PROT_NONE) will still keep the memory allocated if it
123 was written to, munmap is needed, but the following mmap may not find the same segment
124 free...)
126 *) memzero the major fragments after restarting the world and optionally a smaller
127 chunk at a time
129 *) investigate having fragment zeroing threads
131 *) separate locks for finalization and other minor stuff to reduce
132 lock contention
134 *) try a different copying order to improve memory locality
136 *) a thread abort after a store but before the write barrier will
137 prevent the write barrier from executing
139 *) specialized dynamically generated markers/copiers
141 *) Dynamically adjust TLAB size to the number of threads. If we have
142 too many threads that do allocation, we might need smaller TLABs,
143 and we might get better performance with larger TLABs if we only
144 have a handful of threads. We could sum up the space left in all
145 assigned TLABs and if that's more than some percentage of the
146 nursery size, reduce the TLAB size.
148 *) Explore placing unreachable objects on unused nursery memory.
149 Instead of memset'ng a region to zero, place an int[] covering it.
150 A good place to start is add_nursery_frag. The tricky thing here is
151 placing those objects atomically outside of a collection.
153 *) Allocation should use asymmetric Dekker synchronization:
154 http://blogs.oracle.com/dave/resource/Asymmetric-Dekker-Synchronization.txt
155 This should help weak consistency archs.
157 #include "config.h"
158 #ifdef HAVE_SGEN_GC
160 #ifdef __MACH__
161 #undef _XOPEN_SOURCE
162 #define _XOPEN_SOURCE
163 #define _DARWIN_C_SOURCE
164 #endif
166 #ifdef HAVE_UNISTD_H
167 #include <unistd.h>
168 #endif
169 #ifdef HAVE_PTHREAD_H
170 #include <pthread.h>
171 #endif
172 #ifdef HAVE_PTHREAD_NP_H
173 #include <pthread_np.h>
174 #endif
175 #include <stdio.h>
176 #include <string.h>
177 #include <errno.h>
178 #include <assert.h>
179 #include <stdlib.h>
180 #include <glib.h>
182 #include "mono/sgen/sgen-gc.h"
183 #include "mono/sgen/sgen-cardtable.h"
184 #include "mono/sgen/sgen-protocol.h"
185 #include "mono/sgen/sgen-memory-governor.h"
186 #include "mono/sgen/sgen-hash-table.h"
187 #include "mono/sgen/sgen-pinning.h"
188 #include "mono/sgen/sgen-workers.h"
189 #include "mono/sgen/sgen-client.h"
190 #include "mono/sgen/sgen-pointer-queue.h"
191 #include "mono/sgen/gc-internal-agnostic.h"
192 #include "mono/utils/mono-proclib.h"
193 #include "mono/utils/mono-memory-model.h"
194 #include "mono/utils/hazard-pointer.h"
196 #include <mono/utils/memcheck.h>
197 #include <mono/utils/mono-mmap-internals.h>
198 #include <mono/utils/unlocked.h>
200 #undef pthread_create
201 #undef pthread_join
202 #undef pthread_detach
205 * ######################################################################
206 * ######## Types and constants used by the GC.
207 * ######################################################################
210 /* 0 means not initialized, 1 is initialized, -1 means in progress */
211 static int gc_initialized = 0;
212 /* If set, check if we need to do something every X allocations */
213 gboolean sgen_has_per_allocation_action;
214 /* If set, do a heap check every X allocation */
215 guint32 sgen_verify_before_allocs = 0;
216 /* If set, do a minor collection before every X allocation */
217 guint32 sgen_collect_before_allocs = 0;
218 /* If set, do a whole heap check before each collection */
219 static gboolean whole_heap_check_before_collection = FALSE;
220 /* If set, do a remset consistency check at various opportunities */
221 static gboolean remset_consistency_checks = FALSE;
222 /* If set, do a mod union consistency check before each finishing collection pause */
223 static gboolean mod_union_consistency_check = FALSE;
224 /* If set, check whether mark bits are consistent after major collections */
225 static gboolean check_mark_bits_after_major_collection = FALSE;
226 /* If set, check that all vtables of nursery objects are untagged */
227 static gboolean check_nursery_objects_untag = FALSE;
228 /* If set, do a few checks when the concurrent collector is used */
229 static gboolean do_concurrent_checks = FALSE;
230 /* If set, do a plausibility check on the scan_starts before and after
231 each collection */
232 static gboolean do_scan_starts_check = FALSE;
234 static gboolean disable_minor_collections = FALSE;
235 static gboolean disable_major_collections = FALSE;
236 static gboolean do_verify_nursery = FALSE;
237 static gboolean do_dump_nursery_content = FALSE;
238 static gboolean enable_nursery_canaries = FALSE;
240 static gboolean precleaning_enabled = TRUE;
241 static gboolean dynamic_nursery = FALSE;
242 static size_t min_nursery_size = 0;
243 static size_t max_nursery_size = 0;
245 #ifdef HEAVY_STATISTICS
246 guint64 stat_objects_alloced_degraded = 0;
247 guint64 stat_bytes_alloced_degraded = 0;
249 guint64 stat_copy_object_called_nursery = 0;
250 guint64 stat_objects_copied_nursery = 0;
251 guint64 stat_copy_object_called_major = 0;
252 guint64 stat_objects_copied_major = 0;
254 guint64 stat_scan_object_called_nursery = 0;
255 guint64 stat_scan_object_called_major = 0;
257 guint64 stat_slots_allocated_in_vain;
259 guint64 stat_nursery_copy_object_failed_from_space = 0;
260 guint64 stat_nursery_copy_object_failed_forwarded = 0;
261 guint64 stat_nursery_copy_object_failed_pinned = 0;
262 guint64 stat_nursery_copy_object_failed_to_space = 0;
264 static guint64 stat_wbarrier_add_to_global_remset = 0;
265 static guint64 stat_wbarrier_arrayref_copy = 0;
266 static guint64 stat_wbarrier_generic_store = 0;
267 static guint64 stat_wbarrier_generic_store_atomic = 0;
268 static guint64 stat_wbarrier_set_root = 0;
269 #endif
271 static guint64 stat_pinned_objects = 0;
273 static guint64 time_minor_pre_collection_fragment_clear = 0;
274 static guint64 time_minor_pinning = 0;
275 static guint64 time_minor_scan_remsets = 0;
276 static guint64 time_minor_scan_major_blocks = 0;
277 static guint64 time_minor_scan_los = 0;
278 static guint64 time_minor_scan_pinned = 0;
279 static guint64 time_minor_scan_roots = 0;
280 static guint64 time_minor_finish_gray_stack = 0;
281 static guint64 time_minor_fragment_creation = 0;
283 static guint64 time_major_pre_collection_fragment_clear = 0;
284 static guint64 time_major_pinning = 0;
285 static guint64 time_major_scan_pinned = 0;
286 static guint64 time_major_scan_roots = 0;
287 static guint64 time_major_scan_mod_union_blocks = 0;
288 static guint64 time_major_scan_mod_union_los = 0;
289 static guint64 time_major_finish_gray_stack = 0;
290 static guint64 time_major_free_bigobjs = 0;
291 static guint64 time_major_los_sweep = 0;
292 static guint64 time_major_sweep = 0;
293 static guint64 time_major_fragment_creation = 0;
295 static guint64 time_max = 0;
297 static int sgen_max_pause_time = SGEN_DEFAULT_MAX_PAUSE_TIME;
298 static float sgen_max_pause_margin = SGEN_DEFAULT_MAX_PAUSE_MARGIN;
300 static SGEN_TV_DECLARE (time_major_conc_collection_start);
301 static SGEN_TV_DECLARE (time_major_conc_collection_end);
303 int sgen_gc_debug_level = 0;
304 FILE* sgen_gc_debug_file;
305 static char* gc_params_options;
306 static char* gc_debug_options;
309 void
310 mono_gc_flush_info (void)
312 fflush (sgen_gc_debug_file);
316 #define TV_DECLARE SGEN_TV_DECLARE
317 #define TV_GETTIME SGEN_TV_GETTIME
318 #define TV_ELAPSED SGEN_TV_ELAPSED
320 static SGEN_TV_DECLARE (sgen_init_timestamp);
322 NurseryClearPolicy sgen_nursery_clear_policy = CLEAR_AT_TLAB_CREATION;
324 #define object_is_forwarded SGEN_OBJECT_IS_FORWARDED
325 #define object_is_pinned SGEN_OBJECT_IS_PINNED
326 #define pin_object SGEN_PIN_OBJECT
328 #define ptr_in_nursery sgen_ptr_in_nursery
330 #define LOAD_VTABLE SGEN_LOAD_VTABLE
332 gboolean
333 sgen_nursery_canaries_enabled (void)
335 return enable_nursery_canaries;
338 #define safe_object_get_size sgen_safe_object_get_size
340 typedef enum {
341 SGEN_MAJOR_DEFAULT,
342 SGEN_MAJOR_SERIAL,
343 SGEN_MAJOR_CONCURRENT,
344 SGEN_MAJOR_CONCURRENT_PARALLEL
345 } SgenMajor;
347 typedef enum {
348 SGEN_MINOR_DEFAULT,
349 SGEN_MINOR_SIMPLE,
350 SGEN_MINOR_SIMPLE_PARALLEL,
351 SGEN_MINOR_SPLIT
352 } SgenMinor;
354 typedef enum {
355 SGEN_MODE_NONE,
356 SGEN_MODE_BALANCED,
357 SGEN_MODE_THROUGHPUT,
358 SGEN_MODE_PAUSE
359 } SgenMode;
362 * ######################################################################
363 * ######## Global data.
364 * ######################################################################
366 MonoCoopMutex sgen_gc_mutex;
368 #define SCAN_START_SIZE SGEN_SCAN_START_SIZE
370 size_t sgen_degraded_mode = 0;
372 static mword bytes_pinned_from_failed_allocation = 0;
374 GCMemSection *sgen_nursery_section = NULL;
375 static volatile mword lowest_heap_address = ~(mword)0;
376 static volatile mword highest_heap_address = 0;
378 MonoCoopMutex sgen_interruption_mutex;
380 int sgen_current_collection_generation = -1;
381 volatile gboolean sgen_concurrent_collection_in_progress = FALSE;
383 /* objects that are ready to be finalized */
384 static SgenPointerQueue fin_ready_queue = SGEN_POINTER_QUEUE_INIT (INTERNAL_MEM_FINALIZE_READY);
385 static SgenPointerQueue critical_fin_queue = SGEN_POINTER_QUEUE_INIT (INTERNAL_MEM_FINALIZE_READY);
387 /* registered roots: the key to the hash is the root start address */
389 * Different kinds of roots are kept separate to speed up pin_from_roots () for example.
391 SgenHashTable sgen_roots_hash [ROOT_TYPE_NUM] = {
392 SGEN_HASH_TABLE_INIT (INTERNAL_MEM_ROOTS_TABLE, INTERNAL_MEM_ROOT_RECORD, sizeof (RootRecord), sgen_aligned_addr_hash, NULL),
393 SGEN_HASH_TABLE_INIT (INTERNAL_MEM_ROOTS_TABLE, INTERNAL_MEM_ROOT_RECORD, sizeof (RootRecord), sgen_aligned_addr_hash, NULL),
394 SGEN_HASH_TABLE_INIT (INTERNAL_MEM_ROOTS_TABLE, INTERNAL_MEM_ROOT_RECORD, sizeof (RootRecord), sgen_aligned_addr_hash, NULL)
396 static mword roots_size = 0; /* amount of memory in the root set */
398 /* The size of a TLAB */
399 /* The bigger the value, the less often we have to go to the slow path to allocate a new
400 * one, but the more space is wasted by threads not allocating much memory.
401 * FIXME: Tune this.
402 * FIXME: Make this self-tuning for each thread.
404 guint32 sgen_tlab_size = (1024 * 4);
406 #define MAX_SMALL_OBJ_SIZE SGEN_MAX_SMALL_OBJ_SIZE
408 #define ALLOC_ALIGN SGEN_ALLOC_ALIGN
410 #define ALIGN_UP SGEN_ALIGN_UP
412 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
413 MonoNativeThreadId main_gc_thread = NULL;
414 #endif
416 /*Object was pinned during the current collection*/
417 static mword objects_pinned;
420 * ######################################################################
421 * ######## Macros and function declarations.
422 * ######################################################################
425 /* forward declarations */
426 static void scan_from_registered_roots (char *addr_start, char *addr_end, int root_type, ScanCopyContext ctx);
428 static void pin_from_roots (void *start_nursery, void *end_nursery, ScanCopyContext ctx);
429 static void finish_gray_stack (int generation, ScanCopyContext ctx);
432 SgenMajorCollector sgen_major_collector;
433 SgenMinorCollector sgen_minor_collector;
435 static SgenRememberedSet remset;
437 #ifdef MONO_ATOMIC_USES_LOCK
438 #include <pthread.h>
439 static pthread_mutex_t sgen_atomic_spin_lock G_GNUC_UNUSED = PTHREAD_MUTEX_INITIALIZER;
441 static gint64
442 mono_sgen_atomic_cas_i64(volatile gint64 *dest, gint64 exch, gint64 comp)
444 gint64 old;
445 int ret;
447 pthread_cleanup_push ((void(*)(void *))pthread_mutex_unlock, (void *)&sgen_atomic_spin_lock);
448 ret = pthread_mutex_lock(&sgen_atomic_spin_lock);
449 g_assert (ret == 0);
451 old= *dest;
452 if(old==comp) {
453 *dest=exch;
456 ret = pthread_mutex_unlock(&sgen_atomic_spin_lock);
457 g_assert (ret == 0);
459 pthread_cleanup_pop (0);
461 return(old);
463 #endif
466 * The gray queue a worker job must use. If we're not parallel or
467 * concurrent, we use the main gray queue.
469 static SgenGrayQueue*
470 sgen_workers_get_job_gray_queue (WorkerData *worker_data, SgenGrayQueue *default_gray_queue)
472 if (worker_data)
473 return &worker_data->private_gray_queue;
474 SGEN_ASSERT (0, default_gray_queue, "Why don't we have a default gray queue when we're not running in a worker thread?");
475 return default_gray_queue;
478 static void
479 gray_queue_redirect (SgenGrayQueue *queue)
481 sgen_workers_take_from_queue (sgen_current_collection_generation, queue);
484 void
485 sgen_scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data, gboolean allow_flags, gboolean fail_on_canaries)
487 while (start < end) {
488 size_t size;
489 char *obj;
491 if (!*(void**)start) {
492 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
493 continue;
496 if (allow_flags) {
497 if (!(obj = (char *)SGEN_OBJECT_IS_FORWARDED (start)))
498 obj = start;
499 } else {
500 obj = start;
503 if (!sgen_client_object_is_array_fill ((GCObject*)obj)) {
504 CHECK_CANARY_FOR_OBJECT ((GCObject*)obj, fail_on_canaries);
505 size = ALIGN_UP (safe_object_get_size ((GCObject*)obj));
506 callback ((GCObject*)obj, size, data);
507 CANARIFY_SIZE (size);
508 } else {
509 size = ALIGN_UP (safe_object_get_size ((GCObject*)obj));
512 start += size;
517 * sgen_add_to_global_remset:
519 * The global remset contains locations which point into newspace after
520 * a minor collection. This can happen if the objects they point to are pinned.
522 * LOCKING: If called from a parallel collector, the global remset
523 * lock must be held. For serial collectors that is not necessary.
525 void
526 sgen_add_to_global_remset (gpointer ptr, GCObject *obj)
528 SGEN_ASSERT (5, sgen_ptr_in_nursery (obj), "Target pointer of global remset must be in the nursery");
530 HEAVY_STAT (++stat_wbarrier_add_to_global_remset);
532 if (!sgen_major_collector.is_concurrent) {
533 SGEN_ASSERT (5, sgen_current_collection_generation != -1, "Global remsets can only be added during collections");
534 } else {
535 if (sgen_current_collection_generation == -1)
536 SGEN_ASSERT (5, sgen_get_concurrent_collection_in_progress (), "Global remsets outside of collection pauses can only be added by the concurrent collector");
539 if (!object_is_pinned (obj))
540 SGEN_ASSERT (5, sgen_minor_collector.is_split || sgen_get_concurrent_collection_in_progress (), "Non-pinned objects can only remain in nursery if it is a split nursery");
541 else if (sgen_cement_lookup_or_register (obj))
542 return;
544 remset.record_pointer (ptr);
546 sgen_pin_stats_register_global_remset (obj);
548 SGEN_LOG (8, "Adding global remset for %p", ptr);
549 sgen_binary_protocol_global_remset (ptr, obj, (gpointer)SGEN_LOAD_VTABLE (obj));
553 * sgen_drain_gray_stack:
555 * Scan objects in the gray stack until the stack is empty. This should be called
556 * frequently after each object is copied, to achieve better locality and cache
557 * usage.
560 gboolean
561 sgen_drain_gray_stack (ScanCopyContext ctx)
563 SGEN_ASSERT (0, ctx.ops->drain_gray_stack, "Why do we have a scan/copy context with a missing drain gray stack function?");
565 return ctx.ops->drain_gray_stack (ctx.queue);
569 * Addresses in the pin queue are already sorted. This function finds
570 * the object header for each address and pins the object. The
571 * addresses must be inside the nursery section. The (start of the)
572 * address array is overwritten with the addresses of the actually
573 * pinned objects. Return the number of pinned objects.
575 static int
576 pin_objects_from_nursery_pin_queue (gboolean do_scan_objects, ScanCopyContext ctx)
578 GCMemSection *section = sgen_nursery_section;
579 void **start = sgen_pinning_get_entry (section->pin_queue_first_entry);
580 void **end = sgen_pinning_get_entry (section->pin_queue_last_entry);
581 void *start_nursery = section->data;
582 void *end_nursery = section->end_data;
583 void *last = NULL;
584 int count = 0;
585 void *search_start;
586 void *addr;
587 void *pinning_front = start_nursery;
588 size_t idx;
589 void **definitely_pinned = start;
590 ScanObjectFunc scan_func = ctx.ops->scan_object;
591 SgenGrayQueue *queue = ctx.queue;
593 sgen_nursery_allocator_prepare_for_pinning ();
595 while (start < end) {
596 GCObject *obj_to_pin = NULL;
597 size_t obj_to_pin_size = 0;
598 SgenDescriptor desc;
600 addr = *start;
602 SGEN_ASSERT (0, addr >= start_nursery && addr < end_nursery, "Potential pinning address out of range");
603 SGEN_ASSERT (0, addr >= last, "Pin queue not sorted");
605 if (addr == last) {
606 ++start;
607 continue;
610 SGEN_LOG (5, "Considering pinning addr %p", addr);
611 /* We've already processed everything up to pinning_front. */
612 if (addr < pinning_front) {
613 start++;
614 continue;
618 * Find the closest scan start <= addr. We might search backward in the
619 * scan_starts array because entries might be NULL. In the worst case we
620 * start at start_nursery.
622 idx = ((char*)addr - (char*)section->data) / SCAN_START_SIZE;
623 SGEN_ASSERT (0, idx < section->num_scan_start, "Scan start index out of range");
624 search_start = (void*)section->scan_starts [idx];
625 if (!search_start || search_start > addr) {
626 while (idx) {
627 --idx;
628 search_start = section->scan_starts [idx];
629 if (search_start && search_start <= addr)
630 break;
632 if (!search_start || search_start > addr)
633 search_start = start_nursery;
637 * If the pinning front is closer than the scan start we found, start
638 * searching at the front.
640 if (search_start < pinning_front)
641 search_start = pinning_front;
644 * Now addr should be in an object a short distance from search_start.
646 * search_start must point to zeroed mem or point to an object.
648 do {
649 size_t obj_size, canarified_obj_size;
651 /* Skip zeros. */
652 if (!*(void**)search_start) {
653 search_start = (void*)ALIGN_UP ((mword)search_start + sizeof (gpointer));
654 /* The loop condition makes sure we don't overrun addr. */
655 continue;
658 canarified_obj_size = obj_size = ALIGN_UP (safe_object_get_size ((GCObject*)search_start));
661 * Filler arrays are marked by an invalid sync word. We don't
662 * consider them for pinning. They are not delimited by canaries,
663 * either.
665 if (!sgen_client_object_is_array_fill ((GCObject*)search_start)) {
666 CHECK_CANARY_FOR_OBJECT (search_start, TRUE);
667 CANARIFY_SIZE (canarified_obj_size);
669 if (addr >= search_start && (char*)addr < (char*)search_start + obj_size) {
670 /* This is the object we're looking for. */
671 obj_to_pin = (GCObject*)search_start;
672 obj_to_pin_size = canarified_obj_size;
673 break;
677 /* Skip to the next object */
678 search_start = (void*)((char*)search_start + canarified_obj_size);
679 } while (search_start <= addr);
681 /* We've searched past the address we were looking for. */
682 if (!obj_to_pin) {
683 pinning_front = search_start;
684 goto next_pin_queue_entry;
688 * We've found an object to pin. It might still be a dummy array, but we
689 * can advance the pinning front in any case.
691 pinning_front = (char*)obj_to_pin + obj_to_pin_size;
694 * If this is a dummy array marking the beginning of a nursery
695 * fragment, we don't pin it.
697 if (sgen_client_object_is_array_fill (obj_to_pin))
698 goto next_pin_queue_entry;
701 * Finally - pin the object!
703 desc = sgen_obj_get_descriptor_safe (obj_to_pin);
705 if (do_scan_objects) {
706 scan_func (obj_to_pin, desc, queue);
707 } else {
708 SGEN_LOG (4, "Pinned object %p, vtable %p (%s), count %d\n",
709 obj_to_pin, *(void**)obj_to_pin, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (obj_to_pin)), count);
710 sgen_binary_protocol_pin (obj_to_pin,
711 (gpointer)LOAD_VTABLE (obj_to_pin),
712 safe_object_get_size (obj_to_pin));
714 pin_object (obj_to_pin);
715 GRAY_OBJECT_ENQUEUE_SERIAL (queue, obj_to_pin, desc);
716 sgen_pin_stats_register_object (obj_to_pin, GENERATION_NURSERY);
717 definitely_pinned [count] = obj_to_pin;
718 count++;
720 if (sgen_concurrent_collection_in_progress)
721 sgen_pinning_register_pinned_in_nursery (obj_to_pin);
723 next_pin_queue_entry:
724 last = addr;
725 ++start;
727 sgen_client_nursery_objects_pinned (definitely_pinned, count);
728 stat_pinned_objects += count;
729 return count;
732 static void
733 pin_objects_in_nursery (gboolean do_scan_objects, ScanCopyContext ctx)
735 size_t reduced_to;
737 if (sgen_nursery_section->pin_queue_first_entry == sgen_nursery_section->pin_queue_last_entry)
738 return;
740 reduced_to = pin_objects_from_nursery_pin_queue (do_scan_objects, ctx);
741 sgen_nursery_section->pin_queue_last_entry = sgen_nursery_section->pin_queue_first_entry + reduced_to;
745 * This function is only ever called (via `collector_pin_object()` in `sgen-copy-object.h`)
746 * when we can't promote an object because we're out of memory.
748 void
749 sgen_pin_object (GCObject *object, SgenGrayQueue *queue)
751 SGEN_ASSERT (0, sgen_ptr_in_nursery (object), "We're only supposed to use this for pinning nursery objects when out of memory.");
754 * All pinned objects are assumed to have been staged, so we need to stage as well.
755 * Also, the count of staged objects shows that "late pinning" happened.
757 sgen_pin_stage_ptr (object);
759 SGEN_PIN_OBJECT (object);
760 sgen_binary_protocol_pin (object, (gpointer)LOAD_VTABLE (object), safe_object_get_size (object));
762 ++objects_pinned;
763 sgen_pin_stats_register_object (object, GENERATION_NURSERY);
765 GRAY_OBJECT_ENQUEUE_SERIAL (queue, object, sgen_obj_get_descriptor_safe (object));
768 /* Sort the addresses in array in increasing order.
769 * Done using a by-the book heap sort. Which has decent and stable performance, is pretty cache efficient.
771 void
772 sgen_sort_addresses (void **array, size_t size)
774 size_t i;
775 void *tmp;
777 for (i = 1; i < size; ++i) {
778 size_t child = i;
779 while (child > 0) {
780 size_t parent = (child - 1) / 2;
782 if (array [parent] >= array [child])
783 break;
785 tmp = array [parent];
786 array [parent] = array [child];
787 array [child] = tmp;
789 child = parent;
793 for (i = size - 1; i > 0; --i) {
794 size_t end, root;
795 tmp = array [i];
796 array [i] = array [0];
797 array [0] = tmp;
799 end = i - 1;
800 root = 0;
802 while (root * 2 + 1 <= end) {
803 size_t child = root * 2 + 1;
805 if (child < end && array [child] < array [child + 1])
806 ++child;
807 if (array [root] >= array [child])
808 break;
810 tmp = array [root];
811 array [root] = array [child];
812 array [child] = tmp;
814 root = child;
820 * Scan the memory between start and end and queue values which could be pointers
821 * to the area between start_nursery and end_nursery for later consideration.
822 * Typically used for thread stacks.
824 MONO_NO_SANITIZE_ADDRESS
825 void
826 sgen_conservatively_pin_objects_from (void **start, void **end, void *start_nursery, void *end_nursery, int pin_type)
828 int count = 0;
830 SGEN_ASSERT (0, ((mword)start & (SIZEOF_VOID_P - 1)) == 0, "Why are we scanning for references in unaligned memory ?");
832 #if defined(VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE) && !defined(_WIN64)
833 VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE (start, (char*)end - (char*)start);
834 #endif
836 while (start < end) {
838 * *start can point to the middle of an object
839 * note: should we handle pointing at the end of an object?
840 * pinning in C# code disallows pointing at the end of an object
841 * but there is some small chance that an optimizing C compiler
842 * may keep the only reference to an object by pointing
843 * at the end of it. We ignore this small chance for now.
844 * Pointers to the end of an object are indistinguishable
845 * from pointers to the start of the next object in memory
846 * so if we allow that we'd need to pin two objects...
847 * We queue the pointer in an array, the
848 * array will then be sorted and uniqued. This way
849 * we can coalesce several pinning pointers and it should
850 * be faster since we'd do a memory scan with increasing
851 * addresses. Note: we can align the address to the allocation
852 * alignment, so the unique process is more effective.
854 mword addr = (mword)*start;
855 addr &= ~(ALLOC_ALIGN - 1);
856 if (addr >= (mword)start_nursery && addr < (mword)end_nursery) {
857 SGEN_LOG (6, "Pinning address %p from %p", (void*)addr, start);
858 sgen_pin_stage_ptr ((void*)addr);
859 sgen_binary_protocol_pin_stage (start, (void*)addr);
860 sgen_pin_stats_register_address ((char*)addr, pin_type);
861 count++;
863 start++;
865 if (count)
866 SGEN_LOG (7, "found %d potential pinned heap pointers", count);
870 * The first thing we do in a collection is to identify pinned objects.
871 * This function considers all the areas of memory that need to be
872 * conservatively scanned.
874 static void
875 pin_from_roots (void *start_nursery, void *end_nursery, ScanCopyContext ctx)
877 void **start_root;
878 RootRecord *root;
879 SGEN_LOG (2, "Scanning pinned roots (%d bytes, %d/%d entries)", (int)roots_size, sgen_roots_hash [ROOT_TYPE_NORMAL].num_entries, sgen_roots_hash [ROOT_TYPE_PINNED].num_entries);
880 /* objects pinned from the API are inside these roots */
881 SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [ROOT_TYPE_PINNED], void **, start_root, RootRecord *, root) {
882 SGEN_LOG (6, "Pinned roots %p-%p", start_root, root->end_root);
883 sgen_conservatively_pin_objects_from (start_root, (void**)root->end_root, start_nursery, end_nursery, PIN_TYPE_OTHER);
884 } SGEN_HASH_TABLE_FOREACH_END;
885 /* now deal with the thread stacks
886 * in the future we should be able to conservatively scan only:
887 * *) the cpu registers
888 * *) the unmanaged stack frames
889 * *) the _last_ managed stack frame
890 * *) pointers slots in managed frames
892 sgen_client_scan_thread_data (start_nursery, end_nursery, FALSE, ctx);
895 static void
896 single_arg_user_copy_or_mark (GCObject **obj, void *gc_data)
898 ScanCopyContext *ctx = (ScanCopyContext *)gc_data;
899 ctx->ops->copy_or_mark_object (obj, ctx->queue);
903 * The memory area from start_root to end_root contains pointers to objects.
904 * Their position is precisely described by @desc (this means that the pointer
905 * can be either NULL or the pointer to the start of an object).
906 * This functions copies them to to_space updates them.
908 * This function is not thread-safe!
910 static void
911 precisely_scan_objects_from (void** start_root, void** end_root, char* n_start, char *n_end, SgenDescriptor desc, ScanCopyContext ctx)
913 CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object;
914 ScanPtrFieldFunc scan_field_func = ctx.ops->scan_ptr_field;
915 SgenGrayQueue *queue = ctx.queue;
917 switch (desc & ROOT_DESC_TYPE_MASK) {
918 case ROOT_DESC_BITMAP:
919 desc >>= ROOT_DESC_TYPE_SHIFT;
920 while (desc) {
921 if ((desc & 1) && *start_root) {
922 copy_func ((GCObject**)start_root, queue);
923 SGEN_LOG (9, "Overwrote root at %p with %p", start_root, *start_root);
925 desc >>= 1;
926 start_root++;
928 return;
929 case ROOT_DESC_COMPLEX: {
930 gsize *bitmap_data = (gsize *)sgen_get_complex_descriptor_bitmap (desc);
931 gsize bwords = (*bitmap_data) - 1;
932 void **start_run = start_root;
933 bitmap_data++;
934 while (bwords-- > 0) {
935 gsize bmap = *bitmap_data++;
936 void **objptr = start_run;
937 while (bmap) {
938 if ((bmap & 1) && *objptr) {
939 copy_func ((GCObject**)objptr, queue);
940 SGEN_LOG (9, "Overwrote root at %p with %p", objptr, *objptr);
942 bmap >>= 1;
943 ++objptr;
945 start_run += GC_BITS_PER_WORD;
947 break;
949 case ROOT_DESC_VECTOR: {
950 void **p;
952 for (p = start_root; p < end_root; p++) {
953 if (*p)
954 scan_field_func (NULL, (GCObject**)p, queue);
956 break;
958 case ROOT_DESC_USER: {
959 SgenUserRootMarkFunc marker = sgen_get_user_descriptor_func (desc);
960 marker (start_root, single_arg_user_copy_or_mark, &ctx);
961 break;
963 case ROOT_DESC_RUN_LEN:
964 g_assert_not_reached ();
965 default:
966 g_assert_not_reached ();
970 static void
971 reset_heap_boundaries (void)
973 lowest_heap_address = ~(mword)0;
974 highest_heap_address = 0;
977 void
978 sgen_update_heap_boundaries (mword low, mword high)
980 mword old;
982 do {
983 old = lowest_heap_address;
984 if (low >= old)
985 break;
986 } while (SGEN_CAS_PTR ((gpointer*)&lowest_heap_address, (gpointer)low, (gpointer)old) != (gpointer)old);
988 do {
989 old = highest_heap_address;
990 if (high <= old)
991 break;
992 } while (SGEN_CAS_PTR ((gpointer*)&highest_heap_address, (gpointer)high, (gpointer)old) != (gpointer)old);
996 * Allocate and setup the data structures needed to be able to allocate objects
997 * in the nursery. The nursery is stored in sgen_nursery_section.
999 static void
1000 alloc_nursery (gboolean dynamic, size_t min_size, size_t max_size)
1002 char *data;
1003 size_t scan_starts;
1005 if (dynamic) {
1006 if (!min_size)
1007 min_size = SGEN_DEFAULT_NURSERY_MIN_SIZE;
1008 if (!max_size)
1009 max_size = SGEN_DEFAULT_NURSERY_MAX_SIZE;
1010 } else {
1011 SGEN_ASSERT (0, min_size == max_size, "We can't have nursery ranges for static configuration.");
1012 if (!min_size)
1013 min_size = max_size = SGEN_DEFAULT_NURSERY_SIZE;
1016 SGEN_ASSERT (0, !sgen_nursery_section, "Why are we allocating the nursery twice?");
1017 SGEN_LOG (2, "Allocating nursery size: %" G_GSIZE_FORMAT "u, initial %" G_GSIZE_FORMAT "u", max_size, min_size);
1019 /* FIXME: handle OOM */
1020 sgen_nursery_section = (GCMemSection *)sgen_alloc_internal (INTERNAL_MEM_SECTION);
1022 /* If there isn't enough space even for the nursery we should simply abort. */
1023 g_assert (sgen_memgov_try_alloc_space (max_size, SPACE_NURSERY));
1026 * The nursery section range represents the memory section where objects
1027 * can be found. This is used when iterating for objects in the nursery,
1028 * pinning etc. sgen_nursery_max_size represents the total allocated space
1029 * for the nursery. sgen_nursery_size represents the current size of the
1030 * nursery and it is used for allocation limits, heuristics etc. The
1031 * nursery section is not always identical to the current nursery size
1032 * because it can contain pinned objects from when the nursery was larger.
1034 * sgen_nursery_size <= sgen_nursery_section size <= sgen_nursery_max_size
1036 data = (char *)sgen_major_collector.alloc_heap (max_size, max_size);
1037 sgen_update_heap_boundaries ((mword)data, (mword)(data + max_size));
1038 sgen_nursery_section->data = data;
1039 sgen_nursery_section->end_data = data + min_size;
1040 scan_starts = (max_size + SCAN_START_SIZE - 1) / SCAN_START_SIZE;
1041 sgen_nursery_section->scan_starts = (char **)sgen_alloc_internal_dynamic (sizeof (char*) * scan_starts, INTERNAL_MEM_SCAN_STARTS, TRUE);
1042 sgen_nursery_section->num_scan_start = scan_starts;
1044 sgen_nursery_allocator_set_nursery_bounds (data, min_size, max_size);
1047 FILE *
1048 mono_gc_get_logfile (void)
1050 return sgen_gc_debug_file;
1053 void
1054 mono_gc_params_set (const char* options)
1056 if (gc_params_options)
1057 g_free (gc_params_options);
1059 gc_params_options = g_strdup (options);
1062 void
1063 mono_gc_debug_set (const char* options)
1065 if (gc_debug_options)
1066 g_free (gc_debug_options);
1068 gc_debug_options = g_strdup (options);
1071 static void
1072 scan_finalizer_entries (SgenPointerQueue *fin_queue, ScanCopyContext ctx)
1074 CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object;
1075 SgenGrayQueue *queue = ctx.queue;
1076 size_t i;
1078 for (i = 0; i < fin_queue->next_slot; ++i) {
1079 GCObject *obj = (GCObject *)fin_queue->data [i];
1080 if (!obj)
1081 continue;
1082 SGEN_LOG (5, "Scan of fin ready object: %p (%s)\n", obj, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (obj)));
1083 copy_func ((GCObject**)&fin_queue->data [i], queue);
1087 static const char*
1088 generation_name (int generation)
1090 switch (generation) {
1091 case GENERATION_NURSERY: return "nursery";
1092 case GENERATION_OLD: return "old";
1093 default: g_assert_not_reached ();
1097 const char*
1098 sgen_generation_name (int generation)
1100 return generation_name (generation);
1103 static void
1104 finish_gray_stack (int generation, ScanCopyContext ctx)
1106 TV_DECLARE (atv);
1107 TV_DECLARE (btv);
1108 int done_with_ephemerons, ephemeron_rounds = 0;
1109 char *start_addr = generation == GENERATION_NURSERY ? sgen_get_nursery_start () : NULL;
1110 char *end_addr = generation == GENERATION_NURSERY ? sgen_get_nursery_end () : (char*)-1;
1111 SgenGrayQueue *queue = ctx.queue;
1113 sgen_binary_protocol_finish_gray_stack_start (sgen_timestamp (), generation);
1115 * We copied all the reachable objects. Now it's the time to copy
1116 * the objects that were not referenced by the roots, but by the copied objects.
1117 * we built a stack of objects pointed to by gray_start: they are
1118 * additional roots and we may add more items as we go.
1119 * We loop until gray_start == gray_objects which means no more objects have
1120 * been added. Note this is iterative: no recursion is involved.
1121 * We need to walk the LO list as well in search of marked big objects
1122 * (use a flag since this is needed only on major collections). We need to loop
1123 * here as well, so keep a counter of marked LO (increasing it in copy_object).
1124 * To achieve better cache locality and cache usage, we drain the gray stack
1125 * frequently, after each object is copied, and just finish the work here.
1127 sgen_drain_gray_stack (ctx);
1128 TV_GETTIME (atv);
1129 SGEN_LOG (2, "%s generation done", generation_name (generation));
1132 Reset bridge data, we might have lingering data from a previous collection if this is a major
1133 collection trigged by minor overflow.
1135 We must reset the gathered bridges since their original block might be evacuated due to major
1136 fragmentation in the meanwhile and the bridge code should not have to deal with that.
1138 if (sgen_client_bridge_need_processing ())
1139 sgen_client_bridge_reset_data ();
1142 * Mark all strong toggleref objects. This must be done before we walk ephemerons or finalizers
1143 * to ensure they see the full set of live objects.
1145 sgen_client_mark_togglerefs (start_addr, end_addr, ctx);
1148 * Walk the ephemeron tables marking all values with reachable keys. This must be completely done
1149 * before processing finalizable objects and non-tracking weak links to avoid finalizing/clearing
1150 * objects that are in fact reachable.
1152 done_with_ephemerons = 0;
1153 do {
1154 done_with_ephemerons = sgen_client_mark_ephemerons (ctx);
1155 sgen_drain_gray_stack (ctx);
1156 ++ephemeron_rounds;
1157 } while (!done_with_ephemerons);
1159 if (sgen_client_bridge_need_processing ()) {
1160 /*Make sure the gray stack is empty before we process bridge objects so we get liveness right*/
1161 sgen_drain_gray_stack (ctx);
1162 sgen_collect_bridge_objects (generation, ctx);
1163 if (generation == GENERATION_OLD)
1164 sgen_collect_bridge_objects (GENERATION_NURSERY, ctx);
1167 Do the first bridge step here, as the collector liveness state will become useless after that.
1169 An important optimization is to only proccess the possibly dead part of the object graph and skip
1170 over all live objects as we transitively know everything they point must be alive too.
1172 The above invariant is completely wrong if we let the gray queue be drained and mark/copy everything.
1174 This has the unfortunate side effect of making overflow collections perform the first step twice, but
1175 given we now have heuristics that perform major GC in anticipation of minor overflows this should not
1176 be a big deal.
1178 sgen_client_bridge_processing_stw_step ();
1182 Make sure we drain the gray stack before processing disappearing links and finalizers.
1183 If we don't make sure it is empty we might wrongly see a live object as dead.
1185 sgen_drain_gray_stack (ctx);
1188 We must clear weak links that don't track resurrection before processing object ready for
1189 finalization so they can be cleared before that.
1191 sgen_null_link_in_range (generation, ctx, FALSE);
1192 if (generation == GENERATION_OLD)
1193 sgen_null_link_in_range (GENERATION_NURSERY, ctx, FALSE);
1196 /* walk the finalization queue and move also the objects that need to be
1197 * finalized: use the finalized objects as new roots so the objects they depend
1198 * on are also not reclaimed. As with the roots above, only objects in the nursery
1199 * are marked/copied.
1201 sgen_finalize_in_range (generation, ctx);
1202 if (generation == GENERATION_OLD)
1203 sgen_finalize_in_range (GENERATION_NURSERY, ctx);
1204 /* drain the new stack that might have been created */
1205 SGEN_LOG (6, "Precise scan of gray area post fin");
1206 sgen_drain_gray_stack (ctx);
1209 * This must be done again after processing finalizable objects since CWL slots are cleared only after the key is finalized.
1211 done_with_ephemerons = 0;
1212 do {
1213 done_with_ephemerons = sgen_client_mark_ephemerons (ctx);
1214 sgen_drain_gray_stack (ctx);
1215 ++ephemeron_rounds;
1216 } while (!done_with_ephemerons);
1218 sgen_client_clear_unreachable_ephemerons (ctx);
1221 * We clear togglerefs only after all possible chances of revival are done.
1222 * This is semantically more inline with what users expect and it allows for
1223 * user finalizers to correctly interact with TR objects.
1225 sgen_client_clear_togglerefs (start_addr, end_addr, ctx);
1227 TV_GETTIME (btv);
1228 SGEN_LOG (2, "Finalize queue handling scan for %s generation: %" PRId64 " usecs %d ephemeron rounds", generation_name (generation), (gint64)(TV_ELAPSED (atv, btv) / 10), ephemeron_rounds);
1231 * handle disappearing links
1232 * Note we do this after checking the finalization queue because if an object
1233 * survives (at least long enough to be finalized) we don't clear the link.
1234 * This also deals with a possible issue with the monitor reclamation: with the Boehm
1235 * GC a finalized object my lose the monitor because it is cleared before the finalizer is
1236 * called.
1238 g_assert (sgen_gray_object_queue_is_empty (queue));
1239 for (;;) {
1240 sgen_null_link_in_range (generation, ctx, TRUE);
1241 if (generation == GENERATION_OLD)
1242 sgen_null_link_in_range (GENERATION_NURSERY, ctx, TRUE);
1243 if (sgen_gray_object_queue_is_empty (queue))
1244 break;
1245 sgen_drain_gray_stack (ctx);
1248 g_assert (sgen_gray_object_queue_is_empty (queue));
1250 sgen_binary_protocol_finish_gray_stack_end (sgen_timestamp (), generation);
1253 void
1254 sgen_check_section_scan_starts (GCMemSection *section)
1256 size_t i;
1257 for (i = 0; i < section->num_scan_start; ++i) {
1258 if (section->scan_starts [i]) {
1259 mword size = safe_object_get_size ((GCObject*) section->scan_starts [i]);
1260 SGEN_ASSERT (0, size >= SGEN_CLIENT_MINIMUM_OBJECT_SIZE && size <= MAX_SMALL_OBJ_SIZE, "Weird object size at scan starts.");
1265 static void
1266 check_scan_starts (void)
1268 if (!do_scan_starts_check)
1269 return;
1270 sgen_check_section_scan_starts (sgen_nursery_section);
1271 sgen_major_collector.check_scan_starts ();
1274 static void
1275 scan_from_registered_roots (char *addr_start, char *addr_end, int root_type, ScanCopyContext ctx)
1277 void **start_root;
1278 RootRecord *root;
1279 SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [root_type], void **, start_root, RootRecord *, root) {
1280 SGEN_LOG (6, "Precise root scan %p-%p (desc: %p)", start_root, root->end_root, (void*)(uintptr_t)root->root_desc);
1281 precisely_scan_objects_from (start_root, (void**)root->end_root, addr_start, addr_end, root->root_desc, ctx);
1282 } SGEN_HASH_TABLE_FOREACH_END;
1285 static void
1286 init_stats (void)
1288 static gboolean inited = FALSE;
1290 if (inited)
1291 return;
1293 mono_counters_register ("Collection max time", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME | MONO_COUNTER_MONOTONIC, &time_max);
1295 mono_counters_register ("Minor fragment clear", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_pre_collection_fragment_clear);
1296 mono_counters_register ("Minor pinning", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_pinning);
1297 mono_counters_register ("Minor scan remembered set", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_scan_remsets);
1298 mono_counters_register ("Minor scan major blocks", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_scan_major_blocks);
1299 mono_counters_register ("Minor scan los", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_scan_los);
1300 mono_counters_register ("Minor scan pinned", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_scan_pinned);
1301 mono_counters_register ("Minor scan roots", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_scan_roots);
1302 mono_counters_register ("Minor fragment creation", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_minor_fragment_creation);
1304 mono_counters_register ("Major fragment clear", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_pre_collection_fragment_clear);
1305 mono_counters_register ("Major pinning", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_pinning);
1306 mono_counters_register ("Major scan pinned", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_scan_pinned);
1307 mono_counters_register ("Major scan roots", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_scan_roots);
1308 mono_counters_register ("Major scan mod union blocks", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_scan_mod_union_blocks);
1309 mono_counters_register ("Major scan mod union los", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_scan_mod_union_los);
1310 mono_counters_register ("Major finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_finish_gray_stack);
1311 mono_counters_register ("Major free big objects", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_free_bigobjs);
1312 mono_counters_register ("Major LOS sweep", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_los_sweep);
1313 mono_counters_register ("Major sweep", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_sweep);
1314 mono_counters_register ("Major fragment creation", MONO_COUNTER_GC | MONO_COUNTER_ULONG | MONO_COUNTER_TIME, &time_major_fragment_creation);
1316 mono_counters_register ("Number of pinned objects", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_pinned_objects);
1318 #ifdef HEAVY_STATISTICS
1319 mono_counters_register ("WBarrier remember pointer", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wbarrier_add_to_global_remset);
1320 mono_counters_register ("WBarrier arrayref copy", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wbarrier_arrayref_copy);
1321 mono_counters_register ("WBarrier generic store called", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wbarrier_generic_store);
1322 mono_counters_register ("WBarrier generic atomic store called", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wbarrier_generic_store_atomic);
1323 mono_counters_register ("WBarrier set root", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wbarrier_set_root);
1325 mono_counters_register ("# objects allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_objects_alloced_degraded);
1326 mono_counters_register ("bytes allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_bytes_alloced_degraded);
1328 mono_counters_register ("# copy_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_copy_object_called_nursery);
1329 mono_counters_register ("# objects copied (nursery)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_objects_copied_nursery);
1330 mono_counters_register ("# copy_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_copy_object_called_major);
1331 mono_counters_register ("# objects copied (major)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_objects_copied_major);
1333 mono_counters_register ("# scan_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_scan_object_called_nursery);
1334 mono_counters_register ("# scan_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_scan_object_called_major);
1336 mono_counters_register ("Slots allocated in vain", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_slots_allocated_in_vain);
1338 mono_counters_register ("# nursery copy_object() failed from space", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_copy_object_failed_from_space);
1339 mono_counters_register ("# nursery copy_object() failed forwarded", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_copy_object_failed_forwarded);
1340 mono_counters_register ("# nursery copy_object() failed pinned", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_copy_object_failed_pinned);
1341 mono_counters_register ("# nursery copy_object() failed to space", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_copy_object_failed_to_space);
1343 sgen_nursery_allocator_init_heavy_stats ();
1344 #endif
1346 inited = TRUE;
1350 static void
1351 reset_pinned_from_failed_allocation (void)
1353 bytes_pinned_from_failed_allocation = 0;
1356 void
1357 sgen_set_pinned_from_failed_allocation (mword objsize)
1359 bytes_pinned_from_failed_allocation += objsize;
1362 gboolean
1363 sgen_collection_is_concurrent (void)
1365 switch (sgen_current_collection_generation) {
1366 case GENERATION_NURSERY:
1367 return FALSE;
1368 case GENERATION_OLD:
1369 return sgen_concurrent_collection_in_progress;
1370 default:
1371 g_error ("Invalid current generation %d", sgen_current_collection_generation);
1373 return FALSE;
1376 gboolean
1377 sgen_get_concurrent_collection_in_progress (void)
1379 return sgen_concurrent_collection_in_progress;
1382 typedef struct {
1383 SgenThreadPoolJob job;
1384 SgenObjectOperations *ops;
1385 SgenGrayQueue *gc_thread_gray_queue;
1386 } ScanJob;
1388 typedef struct {
1389 ScanJob scan_job;
1390 int job_index, job_split_count;
1391 int data;
1392 } ParallelScanJob;
1394 static ScanCopyContext
1395 scan_copy_context_for_scan_job (void *worker_data_untyped, ScanJob *job)
1397 WorkerData *worker_data = (WorkerData *)worker_data_untyped;
1399 if (!job->ops) {
1401 * For jobs enqueued on workers we set the ops at job runtime in order
1402 * to be able to profit from on the fly optimized object ops or other
1403 * object ops changes, like forced concurrent finish.
1405 SGEN_ASSERT (0, sgen_workers_is_worker_thread (mono_native_thread_id_get ()), "We need a context for the scan job");
1406 job->ops = sgen_workers_get_idle_func_object_ops (worker_data);
1409 return CONTEXT_FROM_OBJECT_OPERATIONS (job->ops, sgen_workers_get_job_gray_queue (worker_data, job->gc_thread_gray_queue));
1412 typedef struct {
1413 ScanJob scan_job;
1414 char *heap_start;
1415 char *heap_end;
1416 int root_type;
1417 } ScanFromRegisteredRootsJob;
1419 static void
1420 job_scan_from_registered_roots (void *worker_data_untyped, SgenThreadPoolJob *job)
1422 ScanFromRegisteredRootsJob *job_data = (ScanFromRegisteredRootsJob*)job;
1423 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, &job_data->scan_job);
1425 scan_from_registered_roots (job_data->heap_start, job_data->heap_end, job_data->root_type, ctx);
1428 typedef struct {
1429 ScanJob scan_job;
1430 char *heap_start;
1431 char *heap_end;
1432 } ScanThreadDataJob;
1434 static void
1435 job_scan_thread_data (void *worker_data_untyped, SgenThreadPoolJob *job)
1437 ScanThreadDataJob *job_data = (ScanThreadDataJob*)job;
1438 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, &job_data->scan_job);
1440 sgen_client_scan_thread_data (job_data->heap_start, job_data->heap_end, TRUE, ctx);
1443 typedef struct {
1444 ScanJob scan_job;
1445 SgenPointerQueue *queue;
1446 } ScanFinalizerEntriesJob;
1448 static void
1449 job_scan_finalizer_entries (void *worker_data_untyped, SgenThreadPoolJob *job)
1451 ScanFinalizerEntriesJob *job_data = (ScanFinalizerEntriesJob*)job;
1452 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, &job_data->scan_job);
1454 scan_finalizer_entries (job_data->queue, ctx);
1457 static void
1458 job_scan_wbroots (void *worker_data_untyped, SgenThreadPoolJob *job)
1460 ScanJob *job_data = (ScanJob*)job;
1461 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, job_data);
1463 sgen_wbroots_scan_card_table (ctx);
1466 static void
1467 job_scan_major_card_table (void *worker_data_untyped, SgenThreadPoolJob *job)
1469 SGEN_TV_DECLARE (atv);
1470 SGEN_TV_DECLARE (btv);
1471 ParallelScanJob *job_data = (ParallelScanJob*)job;
1472 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1474 SGEN_TV_GETTIME (atv);
1475 sgen_major_collector.scan_card_table (CARDTABLE_SCAN_GLOBAL, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
1476 SGEN_TV_GETTIME (btv);
1478 gint64 elapsed_time = SGEN_TV_ELAPSED (atv, btv);
1479 SGEN_ATOMIC_ADD_I64 (time_minor_scan_major_blocks, elapsed_time);
1481 if (worker_data_untyped)
1482 ((WorkerData*)worker_data_untyped)->major_scan_time += elapsed_time;
1485 static void
1486 job_scan_los_card_table (void *worker_data_untyped, SgenThreadPoolJob *job)
1488 SGEN_TV_DECLARE (atv);
1489 SGEN_TV_DECLARE (btv);
1490 ParallelScanJob *job_data = (ParallelScanJob*)job;
1491 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1493 SGEN_TV_GETTIME (atv);
1494 sgen_los_scan_card_table (CARDTABLE_SCAN_GLOBAL, ctx, job_data->job_index, job_data->job_split_count);
1495 SGEN_TV_GETTIME (btv);
1497 gint64 elapsed_time = SGEN_TV_ELAPSED (atv, btv);
1498 SGEN_ATOMIC_ADD_I64 (time_minor_scan_los, elapsed_time);
1500 if (worker_data_untyped)
1501 ((WorkerData*)worker_data_untyped)->los_scan_time += elapsed_time;
1504 static void
1505 job_scan_major_mod_union_card_table (void *worker_data_untyped, SgenThreadPoolJob *job)
1507 SGEN_TV_DECLARE (atv);
1508 SGEN_TV_DECLARE (btv);
1509 ParallelScanJob *job_data = (ParallelScanJob*)job;
1510 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1512 g_assert (sgen_concurrent_collection_in_progress);
1513 SGEN_TV_GETTIME (atv);
1514 sgen_major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
1515 SGEN_TV_GETTIME (btv);
1517 gint64 elapsed_time = SGEN_TV_ELAPSED (atv, btv);
1518 SGEN_ATOMIC_ADD_I64 (time_minor_scan_los, time_major_scan_mod_union_blocks);
1520 if (worker_data_untyped)
1521 ((WorkerData*)worker_data_untyped)->major_scan_time += elapsed_time;
1524 static void
1525 job_scan_los_mod_union_card_table (void *worker_data_untyped, SgenThreadPoolJob *job)
1527 SGEN_TV_DECLARE (atv);
1528 SGEN_TV_DECLARE (btv);
1529 ParallelScanJob *job_data = (ParallelScanJob*)job;
1530 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1532 g_assert (sgen_concurrent_collection_in_progress);
1533 SGEN_TV_GETTIME (atv);
1534 sgen_los_scan_card_table (CARDTABLE_SCAN_MOD_UNION, ctx, job_data->job_index, job_data->job_split_count);
1535 SGEN_TV_GETTIME (btv);
1537 gint64 elapsed_time = SGEN_TV_ELAPSED (atv, btv);
1538 SGEN_ATOMIC_ADD_I64 (time_minor_scan_los, time_major_scan_mod_union_los);
1540 if (worker_data_untyped)
1541 ((WorkerData*)worker_data_untyped)->los_scan_time += elapsed_time;
1544 static void
1545 job_major_mod_union_preclean (void *worker_data_untyped, SgenThreadPoolJob *job)
1547 SGEN_TV_DECLARE (atv);
1548 SGEN_TV_DECLARE (btv);
1549 ParallelScanJob *job_data = (ParallelScanJob*)job;
1550 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1552 g_assert (sgen_concurrent_collection_in_progress);
1553 SGEN_TV_GETTIME (atv);
1554 sgen_major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx, job_data->job_index, job_data->job_split_count, job_data->data);
1555 SGEN_TV_GETTIME (btv);
1557 g_assert (worker_data_untyped);
1558 ((WorkerData*)worker_data_untyped)->major_scan_time += SGEN_TV_ELAPSED (atv, btv);
1561 static void
1562 job_los_mod_union_preclean (void *worker_data_untyped, SgenThreadPoolJob *job)
1564 SGEN_TV_DECLARE (atv);
1565 SGEN_TV_DECLARE (btv);
1566 ParallelScanJob *job_data = (ParallelScanJob*)job;
1567 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, (ScanJob*)job_data);
1569 g_assert (sgen_concurrent_collection_in_progress);
1570 SGEN_TV_GETTIME (atv);
1571 sgen_los_scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx, job_data->job_index, job_data->job_split_count);
1572 SGEN_TV_GETTIME (btv);
1574 g_assert (worker_data_untyped);
1575 ((WorkerData*)worker_data_untyped)->los_scan_time += SGEN_TV_ELAPSED (atv, btv);
1578 static void
1579 job_scan_last_pinned (void *worker_data_untyped, SgenThreadPoolJob *job)
1581 ScanJob *job_data = (ScanJob*)job;
1582 ScanCopyContext ctx = scan_copy_context_for_scan_job (worker_data_untyped, job_data);
1584 g_assert (sgen_concurrent_collection_in_progress);
1586 sgen_scan_pin_queue_objects (ctx);
1589 static void
1590 workers_finish_callback (void)
1592 ParallelScanJob *psj;
1593 ScanJob *sj;
1594 size_t num_major_sections = sgen_major_collector.get_num_major_sections ();
1595 int split_count = sgen_workers_get_job_split_count (GENERATION_OLD);
1596 int i;
1597 /* Mod union preclean jobs */
1598 for (i = 0; i < split_count; i++) {
1599 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("preclean major mod union cardtable", job_major_mod_union_preclean, sizeof (ParallelScanJob));
1600 psj->scan_job.gc_thread_gray_queue = NULL;
1601 psj->job_index = i;
1602 psj->job_split_count = split_count;
1603 psj->data = num_major_sections / split_count;
1604 sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, TRUE);
1607 for (i = 0; i < split_count; i++) {
1608 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("preclean los mod union cardtable", job_los_mod_union_preclean, sizeof (ParallelScanJob));
1609 psj->scan_job.gc_thread_gray_queue = NULL;
1610 psj->job_index = i;
1611 psj->job_split_count = split_count;
1612 sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, TRUE);
1615 sj = (ScanJob*)sgen_thread_pool_job_alloc ("scan last pinned", job_scan_last_pinned, sizeof (ScanJob));
1616 sj->gc_thread_gray_queue = NULL;
1617 sgen_workers_enqueue_job (GENERATION_OLD, &sj->job, TRUE);
1620 static void
1621 init_gray_queue (SgenGrayQueue *gc_thread_gray_queue)
1623 sgen_gray_object_queue_init (gc_thread_gray_queue, NULL, TRUE);
1626 static void
1627 enqueue_scan_remembered_set_jobs (SgenGrayQueue *gc_thread_gray_queue, SgenObjectOperations *ops, gboolean enqueue)
1629 int i, split_count = sgen_workers_get_job_split_count (GENERATION_NURSERY);
1630 size_t num_major_sections = sgen_major_collector.get_num_major_sections ();
1631 ScanJob *sj;
1633 sj = (ScanJob*)sgen_thread_pool_job_alloc ("scan wbroots", job_scan_wbroots, sizeof (ScanJob));
1634 sj->ops = ops;
1635 sj->gc_thread_gray_queue = gc_thread_gray_queue;
1636 sgen_workers_enqueue_job (GENERATION_NURSERY, &sj->job, enqueue);
1638 for (i = 0; i < split_count; i++) {
1639 ParallelScanJob *psj;
1641 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan major remsets", job_scan_major_card_table, sizeof (ParallelScanJob));
1642 psj->scan_job.ops = ops;
1643 psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1644 psj->job_index = i;
1645 psj->job_split_count = split_count;
1646 psj->data = num_major_sections / split_count;
1647 sgen_workers_enqueue_job (GENERATION_NURSERY, &psj->scan_job.job, enqueue);
1649 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan LOS remsets", job_scan_los_card_table, sizeof (ParallelScanJob));
1650 psj->scan_job.ops = ops;
1651 psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1652 psj->job_index = i;
1653 psj->job_split_count = split_count;
1654 sgen_workers_enqueue_job (GENERATION_NURSERY, &psj->scan_job.job, enqueue);
1658 static void
1659 enqueue_scan_from_roots_jobs (SgenGrayQueue *gc_thread_gray_queue, char *heap_start, char *heap_end, SgenObjectOperations *ops, gboolean enqueue)
1661 ScanFromRegisteredRootsJob *scrrj;
1662 ScanThreadDataJob *stdj;
1663 ScanFinalizerEntriesJob *sfej;
1665 /* registered roots, this includes static fields */
1667 scrrj = (ScanFromRegisteredRootsJob*)sgen_thread_pool_job_alloc ("scan from registered roots normal", job_scan_from_registered_roots, sizeof (ScanFromRegisteredRootsJob));
1668 scrrj->scan_job.ops = ops;
1669 scrrj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1670 scrrj->heap_start = heap_start;
1671 scrrj->heap_end = heap_end;
1672 scrrj->root_type = ROOT_TYPE_NORMAL;
1673 sgen_workers_enqueue_job (sgen_current_collection_generation, &scrrj->scan_job.job, enqueue);
1675 if (sgen_current_collection_generation == GENERATION_OLD) {
1676 /* During minors we scan the cardtable for these roots instead */
1677 scrrj = (ScanFromRegisteredRootsJob*)sgen_thread_pool_job_alloc ("scan from registered roots wbarrier", job_scan_from_registered_roots, sizeof (ScanFromRegisteredRootsJob));
1678 scrrj->scan_job.ops = ops;
1679 scrrj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1680 scrrj->heap_start = heap_start;
1681 scrrj->heap_end = heap_end;
1682 scrrj->root_type = ROOT_TYPE_WBARRIER;
1683 sgen_workers_enqueue_job (sgen_current_collection_generation, &scrrj->scan_job.job, enqueue);
1686 /* Threads */
1688 stdj = (ScanThreadDataJob*)sgen_thread_pool_job_alloc ("scan thread data", job_scan_thread_data, sizeof (ScanThreadDataJob));
1689 stdj->scan_job.ops = ops;
1690 stdj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1691 stdj->heap_start = heap_start;
1692 stdj->heap_end = heap_end;
1693 sgen_workers_enqueue_job (sgen_current_collection_generation, &stdj->scan_job.job, enqueue);
1695 /* Scan the list of objects ready for finalization. */
1697 sfej = (ScanFinalizerEntriesJob*)sgen_thread_pool_job_alloc ("scan finalizer entries", job_scan_finalizer_entries, sizeof (ScanFinalizerEntriesJob));
1698 sfej->scan_job.ops = ops;
1699 sfej->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1700 sfej->queue = &fin_ready_queue;
1701 sgen_workers_enqueue_job (sgen_current_collection_generation, &sfej->scan_job.job, enqueue);
1703 sfej = (ScanFinalizerEntriesJob*)sgen_thread_pool_job_alloc ("scan critical finalizer entries", job_scan_finalizer_entries, sizeof (ScanFinalizerEntriesJob));
1704 sfej->scan_job.ops = ops;
1705 sfej->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
1706 sfej->queue = &critical_fin_queue;
1707 sgen_workers_enqueue_job (sgen_current_collection_generation, &sfej->scan_job.job, enqueue);
1711 * Perform a nursery collection.
1713 * Return whether any objects were late-pinned due to being out of memory.
1715 static gboolean
1716 collect_nursery (const char *reason, gboolean is_overflow)
1718 gboolean needs_major, is_parallel = FALSE;
1719 mword fragment_total;
1720 SgenGrayQueue gc_thread_gray_queue;
1721 SgenObjectOperations *object_ops_nopar, *object_ops_par = NULL;
1722 ScanCopyContext ctx;
1723 TV_DECLARE (atv);
1724 TV_DECLARE (btv);
1725 SGEN_TV_DECLARE (last_minor_collection_start_tv);
1726 SGEN_TV_DECLARE (last_minor_collection_end_tv);
1727 guint64 major_scan_start = time_minor_scan_major_blocks;
1728 guint64 los_scan_start = time_minor_scan_los;
1729 guint64 finish_gray_start = time_minor_finish_gray_stack;
1731 if (disable_minor_collections)
1732 return TRUE;
1734 TV_GETTIME (last_minor_collection_start_tv);
1735 atv = last_minor_collection_start_tv;
1737 sgen_binary_protocol_collection_begin (mono_atomic_load_i32 (&mono_gc_stats.minor_gc_count), GENERATION_NURSERY);
1739 object_ops_nopar = sgen_get_concurrent_collection_in_progress ()
1740 ? &sgen_minor_collector.serial_ops_with_concurrent_major
1741 : &sgen_minor_collector.serial_ops;
1742 if (sgen_minor_collector.is_parallel && sgen_nursery_size >= SGEN_PARALLEL_MINOR_MIN_NURSERY_SIZE) {
1743 object_ops_par = sgen_get_concurrent_collection_in_progress ()
1744 ? &sgen_minor_collector.parallel_ops_with_concurrent_major
1745 : &sgen_minor_collector.parallel_ops;
1746 is_parallel = TRUE;
1749 if (do_verify_nursery || do_dump_nursery_content)
1750 sgen_debug_verify_nursery (do_dump_nursery_content);
1752 sgen_current_collection_generation = GENERATION_NURSERY;
1754 SGEN_ASSERT (0, !sgen_collection_is_concurrent (), "Why is the nursery collection concurrent?");
1756 reset_pinned_from_failed_allocation ();
1758 check_scan_starts ();
1760 sgen_nursery_alloc_prepare_for_minor ();
1762 sgen_degraded_mode = 0;
1763 objects_pinned = 0;
1765 SGEN_LOG (1, "Start nursery collection %" G_GINT32_FORMAT " %p-%p, size: %d", mono_atomic_load_i32 (&mono_gc_stats.minor_gc_count), sgen_nursery_section->data, sgen_nursery_section->end_data, (int)(sgen_nursery_section->end_data - sgen_nursery_section->data));
1767 /* world must be stopped already */
1768 TV_GETTIME (btv);
1769 time_minor_pre_collection_fragment_clear += TV_ELAPSED (atv, btv);
1771 sgen_client_pre_collection_checks ();
1773 sgen_major_collector.start_nursery_collection ();
1775 sgen_memgov_minor_collection_start ();
1777 init_gray_queue (&gc_thread_gray_queue);
1778 ctx = CONTEXT_FROM_OBJECT_OPERATIONS (object_ops_nopar, &gc_thread_gray_queue);
1780 mono_atomic_inc_i32 (&mono_gc_stats.minor_gc_count);
1782 sgen_process_fin_stage_entries ();
1784 /* pin from pinned handles */
1785 sgen_init_pinning ();
1786 if (sgen_concurrent_collection_in_progress)
1787 sgen_init_pinning_for_conc ();
1788 sgen_client_binary_protocol_mark_start (GENERATION_NURSERY);
1789 pin_from_roots (sgen_nursery_section->data, sgen_nursery_section->end_data, ctx);
1790 /* pin cemented objects */
1791 sgen_pin_cemented_objects ();
1792 /* identify pinned objects */
1793 sgen_optimize_pin_queue ();
1794 sgen_pinning_setup_section (sgen_nursery_section);
1796 pin_objects_in_nursery (FALSE, ctx);
1797 sgen_pinning_trim_queue_to_section (sgen_nursery_section);
1798 if (sgen_concurrent_collection_in_progress)
1799 sgen_finish_pinning_for_conc ();
1801 if (remset_consistency_checks)
1802 sgen_check_remset_consistency ();
1804 if (whole_heap_check_before_collection) {
1805 sgen_clear_nursery_fragments ();
1806 sgen_check_whole_heap (FALSE);
1809 TV_GETTIME (atv);
1810 time_minor_pinning += TV_ELAPSED (btv, atv);
1811 SGEN_LOG (2, "Finding pinned pointers: %" G_GSIZE_FORMAT "d in %" PRId64 " usecs", sgen_get_pinned_count (), (gint64)(TV_ELAPSED (btv, atv) / 10));
1812 SGEN_LOG (4, "Start scan with %" G_GSIZE_FORMAT "d pinned objects", sgen_get_pinned_count ());
1813 sgen_client_pinning_end ();
1815 remset.start_scan_remsets ();
1816 TV_GETTIME (btv);
1818 SGEN_LOG (2, "Minor scan copy/clear remsets: %lld usecs", (long long)(TV_ELAPSED (atv, btv) / 10));
1820 TV_GETTIME (atv);
1821 enqueue_scan_remembered_set_jobs (&gc_thread_gray_queue, is_parallel ? NULL : object_ops_nopar, is_parallel);
1822 TV_GETTIME (btv);
1824 if (!is_parallel) {
1825 time_minor_scan_remsets += TV_ELAPSED (atv, btv);
1826 SGEN_LOG (2, "Old generation scan: %" PRId64 " usecs", (gint64)(TV_ELAPSED (atv, btv) / 10));
1829 sgen_pin_stats_report ();
1830 sgen_gchandle_stats_report ();
1832 TV_GETTIME (atv);
1833 time_minor_scan_pinned += TV_ELAPSED (btv, atv);
1835 enqueue_scan_from_roots_jobs (&gc_thread_gray_queue, sgen_nursery_section->data, sgen_nursery_section->end_data, is_parallel ? NULL : object_ops_nopar, is_parallel);
1837 if (is_parallel) {
1838 gray_queue_redirect (&gc_thread_gray_queue);
1839 sgen_workers_start_all_workers (GENERATION_NURSERY, object_ops_nopar, object_ops_par, NULL);
1840 sgen_workers_join (GENERATION_NURSERY);
1843 TV_GETTIME (btv);
1844 if (!is_parallel) {
1845 time_minor_scan_roots += TV_ELAPSED (atv, btv);
1847 SGEN_LOG (2, "Minor scan roots: %lld usecs",
1848 (long long)(TV_ELAPSED (atv, btv) / 10));
1849 } else {
1850 SGEN_LOG (2, "Minor scan remsets + roots: %lld usecs",
1851 (long long)(TV_ELAPSED (atv, btv) / 10));
1853 SGEN_LOG (2, "Minor scan remsets: accumulated major scan=%lld usecs, accumulated los scan=%lld usecs, workers=%d",
1854 (long long)((time_minor_scan_major_blocks - major_scan_start) / 10),
1855 (long long)((time_minor_scan_los - los_scan_start) / 10),
1856 sgen_workers_get_active_worker_count (GENERATION_NURSERY));
1859 finish_gray_stack (GENERATION_NURSERY, ctx);
1861 TV_GETTIME (atv);
1862 time_minor_finish_gray_stack += TV_ELAPSED (btv, atv);
1863 sgen_client_binary_protocol_mark_end (GENERATION_NURSERY);
1865 if (objects_pinned) {
1866 sgen_optimize_pin_queue ();
1867 sgen_pinning_setup_section (sgen_nursery_section);
1871 * This is the latest point at which we can do this check, because
1872 * sgen_build_nursery_fragments() unpins nursery objects again.
1874 if (remset_consistency_checks)
1875 sgen_check_remset_consistency ();
1878 if (sgen_max_pause_time) {
1879 int duration;
1881 TV_GETTIME (btv);
1882 duration = (int)(TV_ELAPSED (last_minor_collection_start_tv, btv) / 10000);
1883 if (duration > (sgen_max_pause_time * sgen_max_pause_margin))
1884 sgen_resize_nursery (TRUE);
1885 else
1886 sgen_resize_nursery (FALSE);
1887 } else {
1888 sgen_resize_nursery (FALSE);
1892 * This is used by the profiler to report GC roots.
1893 * Invariants: Heap's finished, no more moves left, objects still pinned in nursery.
1895 sgen_client_collecting_minor_report_roots (&fin_ready_queue, &critical_fin_queue);
1897 /* walk the pin_queue, build up the fragment list of free memory, unmark
1898 * pinned objects as we go, memzero() the empty fragments so they are ready for the
1899 * next allocations.
1901 sgen_client_binary_protocol_reclaim_start (GENERATION_NURSERY);
1902 fragment_total = sgen_build_nursery_fragments (sgen_nursery_section);
1903 if (!fragment_total)
1904 sgen_degraded_mode = 1;
1906 /* Clear TLABs for all threads */
1907 sgen_clear_tlabs ();
1909 sgen_client_binary_protocol_reclaim_end (GENERATION_NURSERY);
1910 TV_GETTIME (btv);
1911 time_minor_fragment_creation += TV_ELAPSED (atv, btv);
1912 SGEN_LOG (2, "Fragment creation: %" PRId64 " usecs, %lu bytes available", (gint64)TV_ELAPSED (atv, btv), (unsigned long)fragment_total);
1914 if (remset_consistency_checks)
1915 sgen_check_major_refs ();
1917 sgen_major_collector.finish_nursery_collection ();
1919 TV_GETTIME (last_minor_collection_end_tv);
1920 UnlockedAdd64 (&mono_gc_stats.minor_gc_time, TV_ELAPSED (last_minor_collection_start_tv, last_minor_collection_end_tv));
1922 sgen_debug_dump_heap ("minor", mono_atomic_load_i32 (&mono_gc_stats.minor_gc_count) - 1, NULL);
1924 /* prepare the pin queue for the next collection */
1925 sgen_finish_pinning ();
1926 if (sgen_have_pending_finalizers ()) {
1927 SGEN_LOG (4, "Finalizer-thread wakeup");
1928 sgen_client_finalize_notify ();
1930 sgen_pin_stats_reset ();
1931 /* clear cemented hash */
1932 sgen_cement_clear_below_threshold ();
1934 sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
1936 check_scan_starts ();
1938 sgen_binary_protocol_flush_buffers (FALSE);
1940 sgen_memgov_minor_collection_end (reason, is_overflow);
1942 /*objects are late pinned because of lack of memory, so a major is a good call*/
1943 needs_major = objects_pinned > 0;
1944 sgen_current_collection_generation = -1;
1945 objects_pinned = 0;
1947 if (is_parallel)
1948 sgen_binary_protocol_collection_end_stats (0, 0, time_minor_finish_gray_stack - finish_gray_start);
1949 else
1950 sgen_binary_protocol_collection_end_stats (
1951 time_minor_scan_major_blocks - major_scan_start,
1952 time_minor_scan_los - los_scan_start,
1953 time_minor_finish_gray_stack - finish_gray_start);
1955 sgen_binary_protocol_collection_end (mono_atomic_load_i32 (&mono_gc_stats.minor_gc_count) - 1, GENERATION_NURSERY, 0, 0);
1957 if (check_nursery_objects_untag)
1958 sgen_check_nursery_objects_untag ();
1960 return needs_major;
1963 typedef enum {
1964 COPY_OR_MARK_FROM_ROOTS_SERIAL,
1965 COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT,
1966 COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT
1967 } CopyOrMarkFromRootsMode;
1969 static void
1970 major_copy_or_mark_from_roots (SgenGrayQueue *gc_thread_gray_queue, size_t *old_next_pin_slot, CopyOrMarkFromRootsMode mode, SgenObjectOperations *object_ops_nopar, SgenObjectOperations *object_ops_par)
1972 TV_DECLARE (atv);
1973 TV_DECLARE (btv);
1974 /* FIXME: only use these values for the precise scan
1975 * note that to_space pointers should be excluded anyway...
1977 char *heap_start = NULL;
1978 char *heap_end = (char*)-1;
1979 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (object_ops_nopar, gc_thread_gray_queue);
1980 gboolean concurrent = mode != COPY_OR_MARK_FROM_ROOTS_SERIAL;
1982 SGEN_ASSERT (0, !!concurrent == !!sgen_concurrent_collection_in_progress, "We've been called with the wrong mode.");
1984 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
1985 /*This cleans up unused fragments */
1986 sgen_nursery_allocator_prepare_for_pinning ();
1988 if (do_concurrent_checks)
1989 sgen_debug_check_nursery_is_clean ();
1990 } else {
1991 /* The concurrent collector doesn't touch the nursery. */
1992 sgen_nursery_alloc_prepare_for_major ();
1995 TV_GETTIME (atv);
1997 /* Pinning depends on this */
1998 sgen_clear_nursery_fragments ();
2000 if (whole_heap_check_before_collection)
2001 sgen_check_whole_heap (TRUE);
2003 TV_GETTIME (btv);
2004 time_major_pre_collection_fragment_clear += TV_ELAPSED (atv, btv);
2006 objects_pinned = 0;
2008 sgen_client_pre_collection_checks ();
2010 if (mode != COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
2011 /* Remsets are not useful for a major collection */
2012 remset.clear_cards ();
2015 sgen_process_fin_stage_entries ();
2017 TV_GETTIME (atv);
2018 sgen_init_pinning ();
2019 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT)
2020 sgen_init_pinning_for_conc ();
2021 SGEN_LOG (6, "Collecting pinned addresses");
2022 pin_from_roots ((void*)lowest_heap_address, (void*)highest_heap_address, ctx);
2023 if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
2024 /* Pin cemented objects that were forced */
2025 sgen_pin_cemented_objects ();
2027 sgen_optimize_pin_queue ();
2028 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
2030 * Cemented objects that are in the pinned list will be marked. When
2031 * marking concurrently we won't mark mod-union cards for these objects.
2032 * Instead they will remain cemented until the next major collection,
2033 * when we will recheck if they are still pinned in the roots.
2035 sgen_cement_force_pinned ();
2039 * pin_queue now contains all candidate pointers, sorted and
2040 * uniqued. We must do two passes now to figure out which
2041 * objects are pinned.
2043 * The first is to find within the pin_queue the area for each
2044 * section. This requires that the pin_queue be sorted. We
2045 * also process the LOS objects and pinned chunks here.
2047 * The second, destructive, pass is to reduce the section
2048 * areas to pointers to the actually pinned objects.
2050 SGEN_LOG (6, "Pinning from sections");
2051 /* first pass for the sections */
2052 sgen_find_section_pin_queue_start_end (sgen_nursery_section);
2053 /* identify possible pointers to the insize of large objects */
2054 SGEN_LOG (6, "Pinning from large objects");
2055 sgen_los_pin_objects (gc_thread_gray_queue, mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT);
2057 pin_objects_in_nursery (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT, ctx);
2059 sgen_major_collector.pin_objects (gc_thread_gray_queue);
2060 if (old_next_pin_slot)
2061 *old_next_pin_slot = sgen_get_pinned_count ();
2063 TV_GETTIME (btv);
2064 time_major_pinning += TV_ELAPSED (atv, btv);
2065 SGEN_LOG (2, "Finding pinned pointers: %" G_GSIZE_FORMAT "d in %" PRId64 " usecs", sgen_get_pinned_count (), (gint64)(TV_ELAPSED (atv, btv) / 10));
2066 SGEN_LOG (4, "Start scan with %" G_GSIZE_FORMAT "d pinned objects", sgen_get_pinned_count ());
2067 sgen_client_pinning_end ();
2069 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT)
2070 sgen_finish_pinning_for_conc ();
2072 sgen_major_collector.init_to_space ();
2074 SGEN_ASSERT (0, sgen_workers_all_done (), "Why are the workers not done when we start or finish a major collection?");
2075 if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
2076 if (object_ops_par != NULL)
2077 sgen_workers_set_num_active_workers (GENERATION_OLD, 0);
2078 if (object_ops_par == NULL && sgen_workers_have_idle_work (GENERATION_OLD)) {
2080 * We force the finish of the worker with the new object ops context
2081 * which can also do copying. We need to have finished pinning. On the
2082 * parallel collector, there is no need to drain the private queues
2083 * here, since we can do it as part of the finishing work, achieving
2084 * better work distribution.
2086 sgen_workers_start_all_workers (GENERATION_OLD, object_ops_nopar, object_ops_par, NULL);
2088 sgen_workers_join (GENERATION_OLD);
2092 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
2093 main_gc_thread = mono_native_thread_self ();
2094 #endif
2096 TV_GETTIME (atv);
2097 time_major_scan_pinned += TV_ELAPSED (btv, atv);
2099 enqueue_scan_from_roots_jobs (gc_thread_gray_queue, heap_start, heap_end, object_ops_nopar, FALSE);
2101 TV_GETTIME (btv);
2102 time_major_scan_roots += TV_ELAPSED (atv, btv);
2105 * We start the concurrent worker after pinning and after we scanned the roots
2106 * in order to make sure that the worker does not finish before handling all
2107 * the roots.
2109 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
2110 sgen_workers_set_num_active_workers (GENERATION_OLD, 1);
2111 gray_queue_redirect (gc_thread_gray_queue);
2112 if (precleaning_enabled) {
2113 sgen_workers_start_all_workers (GENERATION_OLD, object_ops_nopar, object_ops_par, workers_finish_callback);
2114 } else {
2115 sgen_workers_start_all_workers (GENERATION_OLD, object_ops_nopar, object_ops_par, NULL);
2119 if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
2120 int i, split_count = sgen_workers_get_job_split_count (GENERATION_OLD);
2121 size_t num_major_sections = sgen_major_collector.get_num_major_sections ();
2122 gboolean parallel = object_ops_par != NULL;
2124 /* If we're not parallel we finish the collection on the gc thread */
2125 if (parallel)
2126 gray_queue_redirect (gc_thread_gray_queue);
2128 /* Mod union card table */
2129 for (i = 0; i < split_count; i++) {
2130 ParallelScanJob *psj;
2132 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan mod union cardtable", job_scan_major_mod_union_card_table, sizeof (ParallelScanJob));
2133 psj->scan_job.ops = parallel ? NULL : object_ops_nopar;
2134 psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
2135 psj->job_index = i;
2136 psj->job_split_count = split_count;
2137 psj->data = num_major_sections / split_count;
2138 sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, parallel);
2140 psj = (ParallelScanJob*)sgen_thread_pool_job_alloc ("scan LOS mod union cardtable", job_scan_los_mod_union_card_table, sizeof (ParallelScanJob));
2141 psj->scan_job.ops = parallel ? NULL : object_ops_nopar;
2142 psj->scan_job.gc_thread_gray_queue = gc_thread_gray_queue;
2143 psj->job_index = i;
2144 psj->job_split_count = split_count;
2145 sgen_workers_enqueue_job (GENERATION_OLD, &psj->scan_job.job, parallel);
2148 if (parallel) {
2150 * If we enqueue a job while workers are running we need to sgen_workers_ensure_awake
2151 * in order to make sure that we are running the idle func and draining all worker
2152 * gray queues. The operation of starting workers implies this, so we start them after
2153 * in order to avoid doing this operation twice. The workers will drain the main gray
2154 * stack that contained roots and pinned objects and also scan the mod union card
2155 * table.
2157 sgen_workers_start_all_workers (GENERATION_OLD, object_ops_nopar, object_ops_par, NULL);
2158 sgen_workers_join (GENERATION_OLD);
2162 sgen_pin_stats_report ();
2164 if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
2165 sgen_finish_pinning ();
2167 sgen_pin_stats_reset ();
2169 if (do_concurrent_checks)
2170 sgen_debug_check_nursery_is_clean ();
2174 static void
2175 major_start_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason, gboolean concurrent, size_t *old_next_pin_slot)
2177 SgenObjectOperations *object_ops_nopar, *object_ops_par = NULL;
2179 if (concurrent) {
2180 g_assert (sgen_major_collector.is_concurrent);
2181 sgen_concurrent_collection_in_progress = TRUE;
2184 sgen_binary_protocol_collection_begin (mono_atomic_load_i32 (&mono_gc_stats.major_gc_count), GENERATION_OLD);
2186 sgen_current_collection_generation = GENERATION_OLD;
2188 sgen_workers_assert_gray_queue_is_empty (GENERATION_OLD);
2190 if (!concurrent)
2191 sgen_cement_reset ();
2193 if (concurrent) {
2194 object_ops_nopar = &sgen_major_collector.major_ops_concurrent_start;
2195 if (sgen_major_collector.is_parallel)
2196 object_ops_par = &sgen_major_collector.major_ops_conc_par_start;
2198 } else {
2199 object_ops_nopar = &sgen_major_collector.major_ops_serial;
2202 reset_pinned_from_failed_allocation ();
2204 sgen_memgov_major_collection_start (concurrent, reason);
2206 //count_ref_nonref_objs ();
2207 //consistency_check ();
2209 check_scan_starts ();
2211 sgen_degraded_mode = 0;
2212 SGEN_LOG (1, "Start major collection %" G_GINT32_FORMAT, mono_atomic_load_i32 (&mono_gc_stats.major_gc_count));
2213 mono_atomic_inc_i32 (&mono_gc_stats.major_gc_count);
2215 if (sgen_major_collector.start_major_collection)
2216 sgen_major_collector.start_major_collection ();
2218 major_copy_or_mark_from_roots (gc_thread_gray_queue, old_next_pin_slot, concurrent ? COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT : COPY_OR_MARK_FROM_ROOTS_SERIAL, object_ops_nopar, object_ops_par);
2221 static void
2222 major_finish_collection (SgenGrayQueue *gc_thread_gray_queue, const char *reason, gboolean is_overflow, size_t old_next_pin_slot, gboolean forced)
2224 ScannedObjectCounts counts;
2225 SgenObjectOperations *object_ops_nopar;
2226 mword fragment_total;
2227 TV_DECLARE (atv);
2228 TV_DECLARE (btv);
2229 guint64 major_scan_start = time_major_scan_mod_union_blocks;
2230 guint64 los_scan_start = time_major_scan_mod_union_los;
2231 guint64 finish_gray_start = time_major_finish_gray_stack;
2233 if (sgen_concurrent_collection_in_progress) {
2234 SgenObjectOperations *object_ops_par = NULL;
2236 object_ops_nopar = &sgen_major_collector.major_ops_concurrent_finish;
2237 if (sgen_major_collector.is_parallel)
2238 object_ops_par = &sgen_major_collector.major_ops_conc_par_finish;
2240 major_copy_or_mark_from_roots (gc_thread_gray_queue, NULL, COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT, object_ops_nopar, object_ops_par);
2242 #ifdef SGEN_DEBUG_INTERNAL_ALLOC
2243 main_gc_thread = NULL;
2244 #endif
2245 } else {
2246 object_ops_nopar = &sgen_major_collector.major_ops_serial;
2249 sgen_workers_assert_gray_queue_is_empty (GENERATION_OLD);
2251 TV_GETTIME (btv);
2252 finish_gray_stack (GENERATION_OLD, CONTEXT_FROM_OBJECT_OPERATIONS (object_ops_nopar, gc_thread_gray_queue));
2253 TV_GETTIME (atv);
2254 time_major_finish_gray_stack += TV_ELAPSED (btv, atv);
2256 SGEN_ASSERT (0, sgen_workers_all_done (), "Can't have workers working after joining");
2258 if (objects_pinned) {
2259 g_assert (!sgen_concurrent_collection_in_progress);
2262 * This is slow, but we just OOM'd.
2264 * See comment at `sgen_pin_queue_clear_discarded_entries` for how the pin
2265 * queue is laid out at this point.
2267 sgen_pin_queue_clear_discarded_entries (sgen_nursery_section, old_next_pin_slot);
2269 * We need to reestablish all pinned nursery objects in the pin queue
2270 * because they're needed for fragment creation. Unpinning happens by
2271 * walking the whole queue, so it's not necessary to reestablish where major
2272 * heap block pins are - all we care is that they're still in there
2273 * somewhere.
2275 sgen_optimize_pin_queue ();
2276 sgen_find_section_pin_queue_start_end (sgen_nursery_section);
2277 objects_pinned = 0;
2280 reset_heap_boundaries ();
2281 sgen_update_heap_boundaries ((mword)sgen_get_nursery_start (), (mword)sgen_get_nursery_end ());
2284 * We collect the roots before unpinning objects in the nursery since we need to have
2285 * object liveness information for ephemeron root reporting.
2287 sgen_client_collecting_major_report_roots (&fin_ready_queue, &critical_fin_queue);
2289 /* walk the pin_queue, build up the fragment list of free memory, unmark
2290 * pinned objects as we go, memzero() the empty fragments so they are ready for the
2291 * next allocations.
2293 fragment_total = sgen_build_nursery_fragments (sgen_nursery_section);
2294 if (!fragment_total)
2295 sgen_degraded_mode = 1;
2296 SGEN_LOG (4, "Free space in nursery after major %ld", (long)fragment_total);
2298 if (do_concurrent_checks && sgen_concurrent_collection_in_progress)
2299 sgen_debug_check_nursery_is_clean ();
2300 if (check_nursery_objects_untag)
2301 sgen_check_nursery_objects_untag ();
2303 /* prepare the pin queue for the next collection */
2304 sgen_finish_pinning ();
2306 /* Clear TLABs for all threads */
2307 sgen_clear_tlabs ();
2309 sgen_pin_stats_reset ();
2311 sgen_cement_clear_below_threshold ();
2313 if (check_mark_bits_after_major_collection)
2314 sgen_check_heap_marked (sgen_concurrent_collection_in_progress);
2316 TV_GETTIME (btv);
2317 time_major_fragment_creation += TV_ELAPSED (atv, btv);
2319 sgen_binary_protocol_sweep_begin (GENERATION_OLD, !sgen_major_collector.sweeps_lazily);
2320 sgen_memgov_major_pre_sweep ();
2322 TV_GETTIME (atv);
2323 time_major_free_bigobjs += TV_ELAPSED (btv, atv);
2325 sgen_los_sweep ();
2327 TV_GETTIME (btv);
2328 time_major_los_sweep += TV_ELAPSED (atv, btv);
2330 sgen_major_collector.sweep ();
2332 sgen_binary_protocol_sweep_end (GENERATION_OLD, !sgen_major_collector.sweeps_lazily);
2334 TV_GETTIME (atv);
2335 time_major_sweep += TV_ELAPSED (btv, atv);
2337 sgen_debug_dump_heap ("major", mono_atomic_load_i32 (&mono_gc_stats.major_gc_count) - 1, reason);
2339 if (sgen_have_pending_finalizers ()) {
2340 SGEN_LOG (4, "Finalizer-thread wakeup");
2341 sgen_client_finalize_notify ();
2344 sgen_memgov_major_collection_end (forced, sgen_concurrent_collection_in_progress, reason, is_overflow);
2345 sgen_current_collection_generation = -1;
2347 memset (&counts, 0, sizeof (ScannedObjectCounts));
2348 sgen_major_collector.finish_major_collection (&counts);
2350 sgen_workers_assert_gray_queue_is_empty (GENERATION_OLD);
2352 SGEN_ASSERT (0, sgen_workers_all_done (), "Can't have workers working after major collection has finished");
2354 check_scan_starts ();
2356 sgen_binary_protocol_flush_buffers (FALSE);
2358 //consistency_check ();
2359 if (sgen_major_collector.is_parallel)
2360 sgen_binary_protocol_collection_end_stats (0, 0, time_major_finish_gray_stack - finish_gray_start);
2361 else
2362 sgen_binary_protocol_collection_end_stats (
2363 time_major_scan_mod_union_blocks - major_scan_start,
2364 time_major_scan_mod_union_los - los_scan_start,
2365 time_major_finish_gray_stack - finish_gray_start);
2367 sgen_binary_protocol_collection_end (mono_atomic_load_i32 (&mono_gc_stats.major_gc_count) - 1, GENERATION_OLD, counts.num_scanned_objects, counts.num_unique_scanned_objects);
2369 if (sgen_concurrent_collection_in_progress)
2370 sgen_concurrent_collection_in_progress = FALSE;
2373 static gboolean
2374 major_do_collection (const char *reason, gboolean is_overflow, gboolean forced)
2376 TV_DECLARE (time_start);
2377 TV_DECLARE (time_end);
2378 size_t old_next_pin_slot;
2379 SgenGrayQueue gc_thread_gray_queue;
2381 if (disable_major_collections)
2382 return FALSE;
2384 if (sgen_major_collector.get_and_reset_num_major_objects_marked) {
2385 long long num_marked = sgen_major_collector.get_and_reset_num_major_objects_marked ();
2386 g_assert (!num_marked);
2389 /* world must be stopped already */
2390 TV_GETTIME (time_start);
2392 init_gray_queue (&gc_thread_gray_queue);
2393 major_start_collection (&gc_thread_gray_queue, reason, FALSE, &old_next_pin_slot);
2394 major_finish_collection (&gc_thread_gray_queue, reason, is_overflow, old_next_pin_slot, forced);
2395 sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
2397 TV_GETTIME (time_end);
2398 UnlockedAdd64 (&mono_gc_stats.major_gc_time, TV_ELAPSED (time_start, time_end));
2400 /* FIXME: also report this to the user, preferably in gc-end. */
2401 if (sgen_major_collector.get_and_reset_num_major_objects_marked)
2402 sgen_major_collector.get_and_reset_num_major_objects_marked ();
2404 return bytes_pinned_from_failed_allocation > 0;
2407 static void
2408 major_start_concurrent_collection (const char *reason)
2410 TV_DECLARE (time_start);
2411 TV_DECLARE (time_end);
2412 long long num_objects_marked;
2413 SgenGrayQueue gc_thread_gray_queue;
2415 if (disable_major_collections)
2416 return;
2418 TV_GETTIME (time_start);
2419 SGEN_TV_GETTIME (time_major_conc_collection_start);
2421 num_objects_marked = sgen_major_collector.get_and_reset_num_major_objects_marked ();
2422 g_assert (num_objects_marked == 0);
2424 sgen_binary_protocol_concurrent_start ();
2426 init_gray_queue (&gc_thread_gray_queue);
2427 // FIXME: store reason and pass it when finishing
2428 major_start_collection (&gc_thread_gray_queue, reason, TRUE, NULL);
2429 sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
2431 num_objects_marked = sgen_major_collector.get_and_reset_num_major_objects_marked ();
2433 TV_GETTIME (time_end);
2434 UnlockedAdd64 (&mono_gc_stats.major_gc_time, TV_ELAPSED (time_start, time_end));
2436 sgen_current_collection_generation = -1;
2440 * Returns whether the major collection has finished.
2442 static gboolean
2443 major_should_finish_concurrent_collection (void)
2445 return sgen_workers_all_done ();
2448 static void
2449 major_update_concurrent_collection (void)
2451 TV_DECLARE (total_start);
2452 TV_DECLARE (total_end);
2454 TV_GETTIME (total_start);
2456 sgen_binary_protocol_concurrent_update ();
2458 sgen_major_collector.update_cardtable_mod_union ();
2459 sgen_los_update_cardtable_mod_union ();
2461 TV_GETTIME (total_end);
2462 UnlockedAdd64 (&mono_gc_stats.major_gc_time, TV_ELAPSED (total_start, total_end));
2465 static void
2466 major_finish_concurrent_collection (gboolean forced)
2468 SgenGrayQueue gc_thread_gray_queue;
2469 TV_DECLARE (total_start);
2470 TV_DECLARE (total_end);
2472 TV_GETTIME (total_start);
2474 sgen_binary_protocol_concurrent_finish ();
2477 * We need to stop all workers since we're updating the cardtable below.
2478 * The workers will be resumed with a finishing pause context to avoid
2479 * additional cardtable and object scanning.
2481 sgen_workers_stop_all_workers (GENERATION_OLD);
2483 SGEN_TV_GETTIME (time_major_conc_collection_end);
2484 UnlockedAdd64 (&mono_gc_stats.major_gc_time_concurrent, SGEN_TV_ELAPSED (time_major_conc_collection_start, time_major_conc_collection_end));
2486 sgen_major_collector.update_cardtable_mod_union ();
2487 sgen_los_update_cardtable_mod_union ();
2489 if (mod_union_consistency_check)
2490 sgen_check_mod_union_consistency ();
2492 sgen_current_collection_generation = GENERATION_OLD;
2493 sgen_cement_reset ();
2494 init_gray_queue (&gc_thread_gray_queue);
2495 major_finish_collection (&gc_thread_gray_queue, "finishing", FALSE, -1, forced);
2496 sgen_gray_object_queue_dispose (&gc_thread_gray_queue);
2498 TV_GETTIME (total_end);
2499 UnlockedAdd64 (&mono_gc_stats.major_gc_time, TV_ELAPSED (total_start, total_end));
2501 sgen_current_collection_generation = -1;
2505 * Ensure an allocation request for @size will succeed by freeing enough memory.
2507 * LOCKING: The GC lock MUST be held.
2509 void
2510 sgen_ensure_free_space (size_t size, int generation)
2512 int generation_to_collect = -1;
2513 const char *reason = NULL;
2514 gboolean forced = FALSE;
2516 if (generation == GENERATION_OLD) {
2517 if (sgen_need_major_collection (size, &forced)) {
2518 reason = "LOS overflow";
2519 generation_to_collect = GENERATION_OLD;
2521 } else {
2522 if (sgen_degraded_mode) {
2523 if (sgen_need_major_collection (size, &forced)) {
2524 reason = "Degraded mode overflow";
2525 generation_to_collect = GENERATION_OLD;
2527 } else if (sgen_need_major_collection (size, &forced)) {
2528 reason = sgen_concurrent_collection_in_progress ? "Forced finish concurrent collection" : "Minor allowance";
2529 generation_to_collect = GENERATION_OLD;
2530 } else {
2531 generation_to_collect = GENERATION_NURSERY;
2532 reason = "Nursery full";
2536 if (generation_to_collect == -1) {
2537 if (sgen_concurrent_collection_in_progress && sgen_workers_all_done ()) {
2538 generation_to_collect = GENERATION_OLD;
2539 reason = "Finish concurrent collection";
2543 if (generation_to_collect == -1)
2544 return;
2545 sgen_perform_collection (size, generation_to_collect, reason, forced, TRUE);
2549 * LOCKING: Assumes the GC lock is held.
2551 static void
2552 sgen_perform_collection_inner (size_t requested_size, int generation_to_collect, const char *reason, gboolean forced_serial, gboolean stw)
2554 TV_DECLARE (gc_total_start);
2555 TV_DECLARE (gc_total_end);
2556 int overflow_generation_to_collect = -1;
2557 int oldest_generation_collected = generation_to_collect;
2558 const char *overflow_reason = NULL;
2559 gboolean finish_concurrent = sgen_concurrent_collection_in_progress && (major_should_finish_concurrent_collection () || generation_to_collect == GENERATION_OLD);
2561 sgen_binary_protocol_collection_requested (generation_to_collect, requested_size, forced_serial ? 1 : 0);
2563 SGEN_ASSERT (0, generation_to_collect == GENERATION_NURSERY || generation_to_collect == GENERATION_OLD, "What generation is this?");
2565 if (stw)
2566 sgen_stop_world (generation_to_collect, forced_serial || !sgen_major_collector.is_concurrent);
2567 else
2568 SGEN_ASSERT (0, sgen_is_world_stopped (), "We can only collect if the world is stopped");
2571 TV_GETTIME (gc_total_start);
2573 // FIXME: extract overflow reason
2574 // FIXME: minor overflow for concurrent case
2575 if (generation_to_collect == GENERATION_NURSERY && !finish_concurrent) {
2576 if (sgen_concurrent_collection_in_progress)
2577 major_update_concurrent_collection ();
2579 if (collect_nursery (reason, FALSE) && !sgen_concurrent_collection_in_progress) {
2580 overflow_generation_to_collect = GENERATION_OLD;
2581 overflow_reason = "Minor overflow";
2583 } else if (finish_concurrent) {
2584 major_finish_concurrent_collection (forced_serial);
2585 oldest_generation_collected = GENERATION_OLD;
2586 if (forced_serial && generation_to_collect == GENERATION_OLD)
2587 major_do_collection (reason, FALSE, TRUE);
2588 } else {
2589 SGEN_ASSERT (0, generation_to_collect == GENERATION_OLD, "We should have handled nursery collections above");
2590 if (sgen_major_collector.is_concurrent && !forced_serial) {
2591 collect_nursery ("Concurrent start", FALSE);
2592 major_start_concurrent_collection (reason);
2593 oldest_generation_collected = GENERATION_NURSERY;
2594 } else if (major_do_collection (reason, FALSE, forced_serial)) {
2595 overflow_generation_to_collect = GENERATION_NURSERY;
2596 overflow_reason = "Excessive pinning";
2600 if (overflow_generation_to_collect != -1) {
2601 SGEN_ASSERT (0, !sgen_concurrent_collection_in_progress, "We don't yet support overflow collections with the concurrent collector");
2604 * We need to do an overflow collection, either because we ran out of memory
2605 * or the nursery is fully pinned.
2608 if (overflow_generation_to_collect == GENERATION_NURSERY)
2609 collect_nursery (overflow_reason, TRUE);
2610 else
2611 major_do_collection (overflow_reason, TRUE, forced_serial);
2613 oldest_generation_collected = MAX (oldest_generation_collected, overflow_generation_to_collect);
2616 SGEN_LOG (2, "Heap size: %lu, LOS size: %lu", (unsigned long)sgen_gc_get_total_heap_allocation (), (unsigned long)sgen_los_memory_usage);
2618 /* this also sets the proper pointers for the next allocation */
2619 if (generation_to_collect == GENERATION_NURSERY && !sgen_can_alloc_size (requested_size)) {
2620 /* TypeBuilder and MonoMethod are killing mcs with fragmentation */
2621 SGEN_LOG (1, "nursery collection didn't find enough room for %" G_GSIZE_FORMAT "d alloc (%" G_GSIZE_FORMAT "d pinned)", requested_size, sgen_get_pinned_count ());
2622 sgen_dump_pin_queue ();
2623 sgen_degraded_mode = 1;
2626 TV_GETTIME (gc_total_end);
2627 time_max = MAX (time_max, TV_ELAPSED (gc_total_start, gc_total_end));
2629 if (stw)
2630 sgen_restart_world (oldest_generation_collected, forced_serial || !sgen_major_collector.is_concurrent);
2633 #ifdef HOST_WASM
2635 typedef struct {
2636 size_t requested_size;
2637 int generation_to_collect;
2638 const char *reason;
2639 } SgenGcRequest;
2641 static SgenGcRequest gc_request;
2643 #include <emscripten.h>
2645 static void
2646 gc_pump_callback (void)
2648 sgen_perform_collection_inner (gc_request.requested_size, gc_request.generation_to_collect, gc_request.reason, TRUE, TRUE);
2649 gc_request.generation_to_collect = 0;
2651 #endif
2653 #ifdef HOST_WASM
2654 extern gboolean mono_wasm_enable_gc;
2655 #endif
2657 void
2658 sgen_perform_collection (size_t requested_size, int generation_to_collect, const char *reason, gboolean forced_serial, gboolean stw)
2660 #ifdef HOST_WASM
2661 if (!mono_wasm_enable_gc) {
2662 g_assert (stw); //can't handle non-stw mode (IE, domain unload)
2663 //we ignore forced_serial
2665 //There's a window for racing where we're executing other bg jobs before the GC, they trigger a GC request and it overrides this one.
2666 //I belive this case to be benign as it will, in the worst case, upgrade a minor to a major collection.
2667 if (gc_request.generation_to_collect <= generation_to_collect) {
2668 gc_request.requested_size = requested_size;
2669 gc_request.generation_to_collect = generation_to_collect;
2670 gc_request.reason = reason;
2671 sgen_client_schedule_background_job (gc_pump_callback);
2674 sgen_degraded_mode = 1; //enable degraded mode so allocation can continue
2675 return;
2677 #endif
2679 sgen_perform_collection_inner (requested_size, generation_to_collect, reason, forced_serial, stw);
2682 * ######################################################################
2683 * ######## Memory allocation from the OS
2684 * ######################################################################
2685 * This section of code deals with getting memory from the OS and
2686 * allocating memory for GC-internal data structures.
2687 * Internal memory can be handled with a freelist for small objects.
2691 * Debug reporting.
2693 G_GNUC_UNUSED static void
2694 report_internal_mem_usage (void)
2696 printf ("Internal memory usage:\n");
2697 sgen_report_internal_mem_usage ();
2698 printf ("Pinned memory usage:\n");
2699 sgen_major_collector.report_pinned_memory_usage ();
2703 * ######################################################################
2704 * ######## Finalization support
2705 * ######################################################################
2709 * This function returns true if @object is either alive and belongs to the
2710 * current collection - major collections are full heap, so old gen objects
2711 * are never alive during a minor collection.
2713 static int
2714 sgen_is_object_alive_and_on_current_collection (GCObject *object)
2716 if (ptr_in_nursery (object))
2717 return sgen_nursery_is_object_alive (object);
2719 if (sgen_current_collection_generation == GENERATION_NURSERY)
2720 return FALSE;
2722 return sgen_major_is_object_alive (object);
2726 gboolean
2727 sgen_gc_is_object_ready_for_finalization (GCObject *object)
2729 return !sgen_is_object_alive (object);
2732 void
2733 sgen_queue_finalization_entry (GCObject *obj)
2735 gboolean critical = sgen_client_object_has_critical_finalizer (obj);
2737 sgen_pointer_queue_add (critical ? &critical_fin_queue : &fin_ready_queue, obj);
2739 sgen_client_object_queued_for_finalization (obj);
2742 gboolean
2743 sgen_object_is_live (GCObject *obj)
2745 return sgen_is_object_alive_and_on_current_collection (obj);
2749 * `System.GC.WaitForPendingFinalizers` first checks `sgen_have_pending_finalizers()` to
2750 * determine whether it can exit quickly. The latter must therefore only return FALSE if
2751 * all finalizers have really finished running.
2753 * `sgen_gc_invoke_finalizers()` first dequeues a finalizable object, and then finalizes it.
2754 * This means that just checking whether the queues are empty leaves the possibility that an
2755 * object might have been dequeued but not yet finalized. That's why we need the additional
2756 * flag `pending_unqueued_finalizer`.
2759 static volatile gboolean pending_unqueued_finalizer = FALSE;
2760 volatile gboolean sgen_suspend_finalizers = FALSE;
2762 void
2763 sgen_set_suspend_finalizers (void)
2765 sgen_suspend_finalizers = TRUE;
2769 sgen_gc_invoke_finalizers (void)
2771 int count = 0;
2773 g_assert (!pending_unqueued_finalizer);
2775 /* FIXME: batch to reduce lock contention */
2776 while (sgen_have_pending_finalizers ()) {
2777 GCObject *obj;
2779 LOCK_GC;
2782 * We need to set `pending_unqueued_finalizer` before dequeing the
2783 * finalizable object.
2785 if (!sgen_pointer_queue_is_empty (&fin_ready_queue)) {
2786 pending_unqueued_finalizer = TRUE;
2787 mono_memory_write_barrier ();
2788 obj = (GCObject *)sgen_pointer_queue_pop (&fin_ready_queue);
2789 } else if (!sgen_pointer_queue_is_empty (&critical_fin_queue)) {
2790 pending_unqueued_finalizer = TRUE;
2791 mono_memory_write_barrier ();
2792 obj = (GCObject *)sgen_pointer_queue_pop (&critical_fin_queue);
2793 } else {
2794 obj = NULL;
2797 if (obj)
2798 SGEN_LOG (7, "Finalizing object %p (%s)", obj, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (obj)));
2800 UNLOCK_GC;
2802 if (!obj)
2803 break;
2805 count++;
2806 /* the object is on the stack so it is pinned */
2807 /*g_print ("Calling finalizer for object: %p (%s)\n", obj, sgen_client_object_safe_name (obj));*/
2808 sgen_client_run_finalize (obj);
2811 if (pending_unqueued_finalizer) {
2812 mono_memory_write_barrier ();
2813 pending_unqueued_finalizer = FALSE;
2816 return count;
2819 gboolean
2820 sgen_have_pending_finalizers (void)
2822 if (sgen_suspend_finalizers)
2823 return FALSE;
2824 return pending_unqueued_finalizer || !sgen_pointer_queue_is_empty (&fin_ready_queue) || !sgen_pointer_queue_is_empty (&critical_fin_queue);
2828 * ######################################################################
2829 * ######## registered roots support
2830 * ######################################################################
2834 * We do not coalesce roots.
2837 sgen_register_root (char *start, size_t size, SgenDescriptor descr, int root_type, MonoGCRootSource source, void *key, const char *msg)
2839 RootRecord new_root;
2840 int i;
2842 sgen_client_root_registered (start, size, source, key, msg);
2844 LOCK_GC;
2845 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
2846 RootRecord *root = (RootRecord *)sgen_hash_table_lookup (&sgen_roots_hash [i], start);
2847 /* we allow changing the size and the descriptor (for thread statics etc) */
2848 if (root) {
2849 size_t old_size = root->end_root - start;
2850 root->end_root = start + size;
2851 SGEN_ASSERT (0, !!root->root_desc == !!descr, "Can't change whether a root is precise or conservative.");
2852 SGEN_ASSERT (0, root->source == source, "Can't change a root's source identifier.");
2853 SGEN_ASSERT (0, !!root->msg == !!msg, "Can't change a root's message.");
2854 root->root_desc = descr;
2855 roots_size += size;
2856 roots_size -= old_size;
2857 UNLOCK_GC;
2858 return TRUE;
2862 new_root.end_root = start + size;
2863 new_root.root_desc = descr;
2864 new_root.source = source;
2865 new_root.msg = msg;
2867 sgen_hash_table_replace (&sgen_roots_hash [root_type], start, &new_root, NULL);
2868 roots_size += size;
2870 SGEN_LOG (3, "Added root for range: %p-%p, descr: %" PRIx64 " (%d/%d bytes)", start, new_root.end_root, (gint64)descr, (int)size, (int)roots_size);
2872 UNLOCK_GC;
2873 return TRUE;
2876 void
2877 sgen_deregister_root (char* addr)
2879 int root_type;
2880 RootRecord root;
2882 sgen_client_root_deregistered (addr);
2884 LOCK_GC;
2885 for (root_type = 0; root_type < ROOT_TYPE_NUM; ++root_type) {
2886 if (sgen_hash_table_remove (&sgen_roots_hash [root_type], addr, &root))
2887 roots_size -= (root.end_root - addr);
2889 UNLOCK_GC;
2892 void
2893 sgen_wbroots_iterate_live_block_ranges (sgen_cardtable_block_callback cb)
2895 void **start_root;
2896 RootRecord *root;
2897 SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [ROOT_TYPE_WBARRIER], void **, start_root, RootRecord *, root) {
2898 cb ((mword)start_root, (mword)root->end_root - (mword)start_root);
2899 } SGEN_HASH_TABLE_FOREACH_END;
2902 /* Root equivalent of sgen_client_cardtable_scan_object */
2903 static void
2904 sgen_wbroot_scan_card_table (void** start_root, mword size, ScanCopyContext ctx)
2906 ScanPtrFieldFunc scan_field_func = ctx.ops->scan_ptr_field;
2907 guint8 *card_data = sgen_card_table_get_card_scan_address ((mword)start_root);
2908 guint8 *card_base = card_data;
2909 mword card_count = sgen_card_table_number_of_cards_in_range ((mword)start_root, size);
2910 guint8 *card_data_end = card_data + card_count;
2911 mword extra_idx = 0;
2912 char *obj_start = (char*)sgen_card_table_align_pointer (start_root);
2913 char *obj_end = (char*)start_root + size;
2914 #ifdef SGEN_HAVE_OVERLAPPING_CARDS
2915 guint8 *overflow_scan_end = NULL;
2916 #endif
2918 #ifdef SGEN_HAVE_OVERLAPPING_CARDS
2919 /*Check for overflow and if so, setup to scan in two steps*/
2920 if (card_data_end >= SGEN_SHADOW_CARDTABLE_END) {
2921 overflow_scan_end = sgen_shadow_cardtable + (card_data_end - SGEN_SHADOW_CARDTABLE_END);
2922 card_data_end = SGEN_SHADOW_CARDTABLE_END;
2925 LOOP_HEAD:
2926 #endif
2928 card_data = sgen_find_next_card (card_data, card_data_end);
2930 for (; card_data < card_data_end; card_data = sgen_find_next_card (card_data + 1, card_data_end)) {
2931 size_t idx = (card_data - card_base) + extra_idx;
2932 char *start = (char*)(obj_start + idx * CARD_SIZE_IN_BYTES);
2933 char *card_end = start + CARD_SIZE_IN_BYTES;
2934 char *elem = start, *first_elem = start;
2937 * Don't clean first and last card on 32bit systems since they
2938 * may also be part from other roots.
2940 if (card_data != card_base && card_data != (card_data_end - 1))
2941 sgen_card_table_prepare_card_for_scanning (card_data);
2943 card_end = MIN (card_end, obj_end);
2945 if (elem < (char*)start_root)
2946 first_elem = elem = (char*)start_root;
2948 for (; elem < card_end; elem += SIZEOF_VOID_P) {
2949 if (*(GCObject**)elem)
2950 scan_field_func (NULL, (GCObject**)elem, ctx.queue);
2953 sgen_binary_protocol_card_scan (first_elem, elem - first_elem);
2956 #ifdef SGEN_HAVE_OVERLAPPING_CARDS
2957 if (overflow_scan_end) {
2958 extra_idx = card_data - card_base;
2959 card_base = card_data = sgen_shadow_cardtable;
2960 card_data_end = overflow_scan_end;
2961 overflow_scan_end = NULL;
2962 goto LOOP_HEAD;
2964 #endif
2967 void
2968 sgen_wbroots_scan_card_table (ScanCopyContext ctx)
2970 void **start_root;
2971 RootRecord *root;
2973 SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [ROOT_TYPE_WBARRIER], void **, start_root, RootRecord *, root) {
2974 SGEN_ASSERT (0, (root->root_desc & ROOT_DESC_TYPE_MASK) == ROOT_DESC_VECTOR, "Unsupported root type");
2976 sgen_wbroot_scan_card_table (start_root, (mword)root->end_root - (mword)start_root, ctx);
2977 } SGEN_HASH_TABLE_FOREACH_END;
2981 * ######################################################################
2982 * ######## Thread handling (stop/start code)
2983 * ######################################################################
2987 sgen_get_current_collection_generation (void)
2989 return sgen_current_collection_generation;
2992 void*
2993 sgen_thread_attach (SgenThreadInfo* info)
2995 info->tlab_start = info->tlab_next = info->tlab_temp_end = info->tlab_real_end = NULL;
2997 sgen_client_thread_attach (info);
2999 return info;
3002 void
3003 sgen_thread_detach_with_lock (SgenThreadInfo *p)
3005 sgen_client_thread_detach_with_lock (p);
3009 * ######################################################################
3010 * ######## Write barriers
3011 * ######################################################################
3015 * Note: the write barriers first do the needed GC work and then do the actual store:
3016 * this way the value is visible to the conservative GC scan after the write barrier
3017 * itself. If a GC interrupts the barrier in the middle, value will be kept alive by
3018 * the conservative scan, otherwise by the remembered set scan.
3022 * mono_gc_wbarrier_arrayref_copy_internal:
3024 void
3025 mono_gc_wbarrier_arrayref_copy_internal (gpointer dest_ptr, gconstpointer src_ptr, int count)
3027 HEAVY_STAT (++stat_wbarrier_arrayref_copy);
3028 /*This check can be done without taking a lock since dest_ptr array is pinned*/
3029 if (ptr_in_nursery (dest_ptr) || count <= 0) {
3030 mono_gc_memmove_aligned (dest_ptr, src_ptr, count * sizeof (gpointer));
3031 return;
3034 #ifdef SGEN_HEAVY_BINARY_PROTOCOL
3035 if (sgen_binary_protocol_is_heavy_enabled ()) {
3036 int i;
3037 for (i = 0; i < count; ++i) {
3038 gpointer dest = (gpointer*)dest_ptr + i;
3039 gpointer obj = *((gpointer*)src_ptr + i);
3040 if (obj)
3041 sgen_binary_protocol_wbarrier (dest, obj, (gpointer)LOAD_VTABLE (obj));
3044 #endif
3046 remset.wbarrier_arrayref_copy (dest_ptr, src_ptr, count);
3050 * mono_gc_wbarrier_generic_nostore_internal:
3052 void
3053 mono_gc_wbarrier_generic_nostore_internal (gpointer ptr)
3055 gpointer obj;
3057 HEAVY_STAT (++stat_wbarrier_generic_store);
3059 sgen_client_wbarrier_generic_nostore_check (ptr);
3061 obj = *(gpointer*)ptr;
3062 if (obj)
3063 sgen_binary_protocol_wbarrier (ptr, obj, (gpointer)LOAD_VTABLE (obj));
3066 * We need to record old->old pointer locations for the
3067 * concurrent collector.
3069 if (!ptr_in_nursery (obj) && !sgen_concurrent_collection_in_progress) {
3070 SGEN_LOG (8, "Skipping remset at %p", ptr);
3071 return;
3074 SGEN_LOG (8, "Adding remset at %p", ptr);
3076 remset.wbarrier_generic_nostore (ptr);
3080 * mono_gc_wbarrier_generic_store_internal:
3082 void
3083 mono_gc_wbarrier_generic_store_internal (void volatile* ptr, GCObject* value)
3085 SGEN_LOG (8, "Wbarrier store at %p to %p (%s)", ptr, value, value ? sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (value)) : "null");
3086 SGEN_UPDATE_REFERENCE_ALLOW_NULL ((void*)ptr, value); // FIXME volatile
3087 if (ptr_in_nursery (value) || sgen_concurrent_collection_in_progress)
3088 mono_gc_wbarrier_generic_nostore_internal ((void*)ptr); // FIXME volatile
3089 sgen_dummy_use (value);
3093 * mono_gc_wbarrier_generic_store_atomic_internal:
3094 * Same as \c mono_gc_wbarrier_generic_store but performs the store
3095 * as an atomic operation with release semantics.
3097 void
3098 mono_gc_wbarrier_generic_store_atomic_internal (gpointer ptr, GCObject *value)
3100 HEAVY_STAT (++stat_wbarrier_generic_store_atomic);
3102 SGEN_LOG (8, "Wbarrier atomic store at %p to %p (%s)", ptr, value, value ? sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (value)) : "null");
3104 mono_atomic_store_ptr ((volatile gpointer *)ptr, value);
3106 if (ptr_in_nursery (value) || sgen_concurrent_collection_in_progress)
3107 mono_gc_wbarrier_generic_nostore_internal (ptr);
3109 sgen_dummy_use (value);
3112 void
3113 sgen_wbarrier_range_copy (gpointer _dest, gconstpointer _src, int size)
3115 remset.wbarrier_range_copy (_dest,_src, size);
3119 * ######################################################################
3120 * ######## Other mono public interface functions.
3121 * ######################################################################
3124 void
3125 sgen_gc_collect (int generation)
3127 gboolean forced;
3129 LOCK_GC;
3130 if (generation > 1)
3131 generation = 1;
3132 sgen_perform_collection (0, generation, "user request", TRUE, TRUE);
3133 /* Make sure we don't exceed heap size allowance by promoting */
3134 if (generation == GENERATION_NURSERY && sgen_need_major_collection (0, &forced))
3135 sgen_perform_collection (0, GENERATION_OLD, "Minor allowance", forced, TRUE);
3136 UNLOCK_GC;
3140 sgen_gc_collection_count (int generation)
3142 return mono_atomic_load_i32 (generation == GENERATION_NURSERY ? &mono_gc_stats.minor_gc_count : &mono_gc_stats.major_gc_count);
3145 size_t
3146 sgen_gc_get_used_size (void)
3148 gint64 tot = 0;
3149 LOCK_GC;
3150 tot = sgen_los_memory_usage;
3151 tot += sgen_nursery_section->end_data - sgen_nursery_section->data;
3152 tot += sgen_major_collector.get_used_size ();
3153 /* FIXME: account for pinned objects */
3154 UNLOCK_GC;
3155 return tot;
3158 void
3159 sgen_env_var_error (const char *env_var, const char *fallback, const char *description_format, ...)
3161 va_list ap;
3163 va_start (ap, description_format);
3165 fprintf (stderr, "Warning: In environment variable `%s': ", env_var);
3166 vfprintf (stderr, description_format, ap);
3167 if (fallback)
3168 fprintf (stderr, " - %s", fallback);
3169 fprintf (stderr, "\n");
3171 va_end (ap);
3174 static gboolean
3175 parse_double_in_interval (const char *env_var, const char *opt_name, const char *opt, double min, double max, double *result)
3177 char *endptr;
3178 double val = strtod (opt, &endptr);
3179 if (endptr == opt) {
3180 sgen_env_var_error (env_var, "Using default value.", "`%s` must be a number.", opt_name);
3181 return FALSE;
3183 else if (val < min || val > max) {
3184 sgen_env_var_error (env_var, "Using default value.", "`%s` must be between %.2f - %.2f.", opt_name, min, max);
3185 return FALSE;
3187 *result = val;
3188 return TRUE;
3191 static SgenMinor
3192 parse_sgen_minor (const char *opt)
3194 if (!opt)
3195 return SGEN_MINOR_DEFAULT;
3197 if (!strcmp (opt, "simple")) {
3198 return SGEN_MINOR_SIMPLE;
3199 } else if (!strcmp (opt, "simple-par")) {
3200 return SGEN_MINOR_SIMPLE_PARALLEL;
3201 } else if (!strcmp (opt, "split")) {
3202 return SGEN_MINOR_SPLIT;
3203 } else {
3204 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default instead.", "Unknown minor collector `%s'.", opt);
3205 return SGEN_MINOR_DEFAULT;
3209 static SgenMajor
3210 parse_sgen_major (const char *opt)
3212 if (!opt)
3213 return SGEN_MAJOR_DEFAULT;
3215 if (!strcmp (opt, "marksweep")) {
3216 return SGEN_MAJOR_SERIAL;
3217 } else if (!strcmp (opt, "marksweep-conc")) {
3218 return SGEN_MAJOR_CONCURRENT;
3219 } else if (!strcmp (opt, "marksweep-conc-par")) {
3220 return SGEN_MAJOR_CONCURRENT_PARALLEL;
3221 } else {
3222 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default instead.", "Unknown major collector `%s'.", opt);
3223 return SGEN_MAJOR_DEFAULT;
3228 static SgenMode
3229 parse_sgen_mode (const char *opt)
3231 if (!opt)
3232 return SGEN_MODE_NONE;
3234 if (!strcmp (opt, "balanced")) {
3235 return SGEN_MODE_BALANCED;
3236 } else if (!strcmp (opt, "throughput")) {
3237 return SGEN_MODE_THROUGHPUT;
3238 } else if (!strcmp (opt, "pause") || g_str_has_prefix (opt, "pause:")) {
3239 return SGEN_MODE_PAUSE;
3240 } else {
3241 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default configurations.", "Unknown mode `%s'.", opt);
3242 return SGEN_MODE_NONE;
3246 static void
3247 init_sgen_minor (SgenMinor minor)
3249 switch (minor) {
3250 case SGEN_MINOR_DEFAULT:
3251 case SGEN_MINOR_SIMPLE:
3252 sgen_simple_nursery_init (&sgen_minor_collector, FALSE);
3253 break;
3254 case SGEN_MINOR_SIMPLE_PARALLEL:
3255 #ifndef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC
3256 sgen_simple_nursery_init (&sgen_minor_collector, TRUE);
3257 #else
3258 g_error ("Sgen was build with concurrent collector disabled");
3259 #endif
3260 break;
3261 case SGEN_MINOR_SPLIT:
3262 #ifndef DISABLE_SGEN_SPLIT_NURSERY
3263 sgen_split_nursery_init (&sgen_minor_collector);
3264 #else
3265 g_error ("Sgenw as build with split nursery disabled");
3266 #endif
3267 break;
3268 default:
3269 g_assert_not_reached ();
3273 static void
3274 init_sgen_major (SgenMajor major)
3276 if (major == SGEN_MAJOR_DEFAULT)
3277 major = DEFAULT_MAJOR;
3279 switch (major) {
3280 case SGEN_MAJOR_SERIAL:
3281 sgen_marksweep_init (&sgen_major_collector);
3282 break;
3283 #ifdef DISABLE_SGEN_MAJOR_MARKSWEEP_CONC
3284 case SGEN_MAJOR_CONCURRENT:
3285 case SGEN_MAJOR_CONCURRENT_PARALLEL:
3286 g_error ("Sgen was build with the concurent collector disabled");
3287 #else
3288 case SGEN_MAJOR_CONCURRENT:
3289 sgen_marksweep_conc_init (&sgen_major_collector);
3290 break;
3291 case SGEN_MAJOR_CONCURRENT_PARALLEL:
3292 sgen_marksweep_conc_par_init (&sgen_major_collector);
3293 break;
3294 #endif
3295 default:
3296 g_assert_not_reached ();
3301 * If sgen mode is set, major/minor configuration is fixed. The other gc_params
3302 * are parsed and processed after major/minor initialization, so it can potentially
3303 * override some knobs set by the sgen mode. We can consider locking out additional
3304 * configurations when gc_modes are used.
3306 static void
3307 init_sgen_mode (SgenMode mode)
3309 SgenMinor minor = SGEN_MINOR_DEFAULT;
3310 SgenMajor major = SGEN_MAJOR_DEFAULT;
3312 switch (mode) {
3313 case SGEN_MODE_BALANCED:
3315 * Use a dynamic parallel nursery with a major concurrent collector.
3316 * This uses the default values for max pause time and nursery size.
3318 minor = SGEN_MINOR_SIMPLE;
3319 major = SGEN_MAJOR_CONCURRENT;
3320 dynamic_nursery = TRUE;
3321 break;
3322 case SGEN_MODE_THROUGHPUT:
3324 * Use concurrent major to let the mutator do more work. Use a larger
3325 * nursery, without pause time constraints, in order to collect more
3326 * objects in parallel and avoid repetitive collection tasks (pinning,
3327 * root scanning etc)
3329 minor = SGEN_MINOR_SIMPLE_PARALLEL;
3330 major = SGEN_MAJOR_CONCURRENT;
3331 dynamic_nursery = TRUE;
3332 sgen_max_pause_time = 0;
3333 break;
3334 case SGEN_MODE_PAUSE:
3336 * Use concurrent major and dynamic nursery with a more
3337 * aggressive shrinking relative to pause times.
3339 minor = SGEN_MINOR_SIMPLE_PARALLEL;
3340 major = SGEN_MAJOR_CONCURRENT;
3341 dynamic_nursery = TRUE;
3342 sgen_max_pause_margin = SGEN_PAUSE_MODE_MAX_PAUSE_MARGIN;
3343 break;
3344 default:
3345 g_assert_not_reached ();
3348 init_sgen_minor (minor);
3349 init_sgen_major (major);
3352 void
3353 sgen_gc_init (void)
3355 char *env;
3356 char **opts, **ptr;
3357 SgenMajor sgen_major = SGEN_MAJOR_DEFAULT;
3358 SgenMinor sgen_minor = SGEN_MINOR_DEFAULT;
3359 SgenMode sgen_mode = SGEN_MODE_NONE;
3360 char *params_opts = NULL;
3361 char *debug_opts = NULL;
3362 size_t max_heap = 0;
3363 size_t soft_limit = 0;
3364 int result;
3365 gboolean debug_print_allowance = FALSE;
3366 double allowance_ratio = 0, save_target = 0;
3367 gboolean cement_enabled = TRUE;
3369 do {
3370 result = mono_atomic_cas_i32 (&gc_initialized, -1, 0);
3371 switch (result) {
3372 case 1:
3373 /* already inited */
3374 return;
3375 case -1:
3376 /* being inited by another thread */
3377 mono_thread_info_usleep (1000);
3378 break;
3379 case 0:
3380 /* we will init it */
3381 break;
3382 default:
3383 g_assert_not_reached ();
3385 } while (result != 0);
3387 SGEN_TV_GETTIME (sgen_init_timestamp);
3389 #ifdef SGEN_WITHOUT_MONO
3390 mono_thread_smr_init ();
3391 #endif
3393 mono_coop_mutex_init (&sgen_gc_mutex);
3395 sgen_gc_debug_file = stderr;
3397 mono_coop_mutex_init (&sgen_interruption_mutex);
3399 if ((env = g_getenv (MONO_GC_PARAMS_NAME)) || gc_params_options) {
3400 params_opts = g_strdup_printf ("%s,%s", gc_params_options ? gc_params_options : "", env ? env : "");
3401 g_free (env);
3404 if (params_opts) {
3405 opts = g_strsplit (params_opts, ",", -1);
3406 for (ptr = opts; *ptr; ++ptr) {
3407 char *opt = *ptr;
3408 if (g_str_has_prefix (opt, "major=")) {
3409 opt = strchr (opt, '=') + 1;
3410 sgen_major = parse_sgen_major (opt);
3411 } else if (g_str_has_prefix (opt, "minor=")) {
3412 opt = strchr (opt, '=') + 1;
3413 sgen_minor = parse_sgen_minor (opt);
3414 } else if (g_str_has_prefix (opt, "mode=")) {
3415 opt = strchr (opt, '=') + 1;
3416 sgen_mode = parse_sgen_mode (opt);
3419 } else {
3420 opts = NULL;
3423 init_stats ();
3424 sgen_init_internal_allocator ();
3425 sgen_init_nursery_allocator ();
3426 sgen_init_fin_weak_hash ();
3427 sgen_init_hash_table ();
3428 sgen_init_descriptors ();
3429 sgen_init_gray_queues ();
3430 sgen_init_allocator ();
3431 sgen_init_gchandles ();
3433 sgen_register_fixed_internal_mem_type (INTERNAL_MEM_SECTION, SGEN_SIZEOF_GC_MEM_SECTION);
3434 sgen_register_fixed_internal_mem_type (INTERNAL_MEM_GRAY_QUEUE, sizeof (GrayQueueSection));
3436 sgen_client_init ();
3438 if (sgen_mode != SGEN_MODE_NONE) {
3439 if (sgen_minor != SGEN_MINOR_DEFAULT || sgen_major != SGEN_MAJOR_DEFAULT)
3440 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Ignoring major/minor configuration", "Major/minor configurations cannot be used with sgen modes");
3441 init_sgen_mode (sgen_mode);
3442 } else {
3443 init_sgen_minor (sgen_minor);
3444 init_sgen_major (sgen_major);
3447 if (opts) {
3448 gboolean usage_printed = FALSE;
3450 for (ptr = opts; *ptr; ++ptr) {
3451 char *opt = *ptr;
3452 if (!strcmp (opt, ""))
3453 continue;
3454 if (g_str_has_prefix (opt, "major="))
3455 continue;
3456 if (g_str_has_prefix (opt, "minor="))
3457 continue;
3458 if (g_str_has_prefix (opt, "mode=")) {
3459 if (g_str_has_prefix (opt, "mode=pause:")) {
3460 char *str_pause = strchr (opt, ':') + 1;
3461 int pause = atoi (str_pause);
3462 if (pause)
3463 sgen_max_pause_time = pause;
3464 else
3465 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default", "Invalid maximum pause time for `pause` sgen mode");
3467 continue;
3469 if (g_str_has_prefix (opt, "max-heap-size=")) {
3470 size_t page_size = mono_pagesize ();
3471 size_t max_heap_candidate = 0;
3472 opt = strchr (opt, '=') + 1;
3473 if (*opt && mono_gc_parse_environment_string_extract_number (opt, &max_heap_candidate)) {
3474 max_heap = (max_heap_candidate + page_size - 1) & ~(size_t)(page_size - 1);
3475 if (max_heap != max_heap_candidate)
3476 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Rounding up.", "`max-heap-size` size must be a multiple of %d.", page_size);
3477 } else {
3478 sgen_env_var_error (MONO_GC_PARAMS_NAME, NULL, "`max-heap-size` must be an integer.");
3480 continue;
3482 if (g_str_has_prefix (opt, "soft-heap-limit=")) {
3483 opt = strchr (opt, '=') + 1;
3484 if (*opt && mono_gc_parse_environment_string_extract_number (opt, &soft_limit)) {
3485 if (soft_limit <= 0) {
3486 sgen_env_var_error (MONO_GC_PARAMS_NAME, NULL, "`soft-heap-limit` must be positive.");
3487 soft_limit = 0;
3489 } else {
3490 sgen_env_var_error (MONO_GC_PARAMS_NAME, NULL, "`soft-heap-limit` must be an integer.");
3492 continue;
3494 if (g_str_has_prefix (opt, "nursery-size=")) {
3495 size_t val;
3496 opt = strchr (opt, '=') + 1;
3497 if (*opt && mono_gc_parse_environment_string_extract_number (opt, &val)) {
3498 if ((val & (val - 1))) {
3499 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default value.", "`nursery-size` must be a power of two.");
3500 continue;
3503 if (val < SGEN_MAX_NURSERY_WASTE) {
3504 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default value.",
3505 "`nursery-size` must be at least %d bytes.", SGEN_MAX_NURSERY_WASTE);
3506 continue;
3508 #ifdef SGEN_MAX_NURSERY_SIZE
3509 if (val > SGEN_MAX_NURSERY_SIZE) {
3510 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default value.",
3511 "`nursery-size` must be smaller than %" PRId64 " bytes.", SGEN_MAX_NURSERY_SIZE);
3512 continue;
3514 #endif
3515 min_nursery_size = max_nursery_size = val;
3516 dynamic_nursery = FALSE;
3517 } else {
3518 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default value.", "`nursery-size` must be an integer.");
3519 continue;
3521 continue;
3523 if (g_str_has_prefix (opt, "save-target-ratio=")) {
3524 double val;
3525 opt = strchr (opt, '=') + 1;
3526 if (parse_double_in_interval (MONO_GC_PARAMS_NAME, "save-target-ratio", opt,
3527 SGEN_MIN_SAVE_TARGET_RATIO, SGEN_MAX_SAVE_TARGET_RATIO, &val)) {
3528 save_target = val;
3530 continue;
3532 if (g_str_has_prefix (opt, "default-allowance-ratio=")) {
3533 double val;
3534 opt = strchr (opt, '=') + 1;
3535 if (parse_double_in_interval (MONO_GC_PARAMS_NAME, "default-allowance-ratio", opt,
3536 SGEN_MIN_ALLOWANCE_NURSERY_SIZE_RATIO, SGEN_MAX_ALLOWANCE_NURSERY_SIZE_RATIO, &val)) {
3537 allowance_ratio = val;
3539 continue;
3542 if (!strcmp (opt, "cementing")) {
3543 cement_enabled = TRUE;
3544 continue;
3546 if (!strcmp (opt, "no-cementing")) {
3547 cement_enabled = FALSE;
3548 continue;
3551 if (!strcmp (opt, "precleaning")) {
3552 precleaning_enabled = TRUE;
3553 continue;
3555 if (!strcmp (opt, "no-precleaning")) {
3556 precleaning_enabled = FALSE;
3557 continue;
3560 if (!strcmp (opt, "dynamic-nursery")) {
3561 if (sgen_minor_collector.is_split)
3562 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using default value.",
3563 "dynamic-nursery not supported with split-nursery.");
3564 else
3565 dynamic_nursery = TRUE;
3566 continue;
3568 if (!strcmp (opt, "no-dynamic-nursery")) {
3569 dynamic_nursery = FALSE;
3570 continue;
3573 if (sgen_major_collector.handle_gc_param && sgen_major_collector.handle_gc_param (opt))
3574 continue;
3576 if (sgen_minor_collector.handle_gc_param && sgen_minor_collector.handle_gc_param (opt))
3577 continue;
3579 if (sgen_client_handle_gc_param (opt))
3580 continue;
3582 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Ignoring.", "Unknown option `%s`.", opt);
3584 if (usage_printed)
3585 continue;
3587 fprintf (stderr, "\n%s must be a comma-delimited list of one or more of the following:\n", MONO_GC_PARAMS_NAME);
3588 fprintf (stderr, " max-heap-size=N (where N is an integer, possibly with a k, m or a g suffix)\n");
3589 fprintf (stderr, " soft-heap-limit=n (where N is an integer, possibly with a k, m or a g suffix)\n");
3590 fprintf (stderr, " mode=MODE (where MODE is 'balanced', 'throughput' or 'pause[:N]' and N is maximum pause in milliseconds)\n");
3591 fprintf (stderr, " nursery-size=N (where N is an integer, possibly with a k, m or a g suffix)\n");
3592 fprintf (stderr, " major=COLLECTOR (where COLLECTOR is `marksweep', `marksweep-conc', `marksweep-par')\n");
3593 fprintf (stderr, " minor=COLLECTOR (where COLLECTOR is `simple' or `split')\n");
3594 fprintf (stderr, " wbarrier=WBARRIER (where WBARRIER is `remset' or `cardtable')\n");
3595 fprintf (stderr, " [no-]cementing\n");
3596 fprintf (stderr, " [no-]dynamic-nursery\n");
3597 if (sgen_major_collector.print_gc_param_usage)
3598 sgen_major_collector.print_gc_param_usage ();
3599 if (sgen_minor_collector.print_gc_param_usage)
3600 sgen_minor_collector.print_gc_param_usage ();
3601 sgen_client_print_gc_params_usage ();
3602 fprintf (stderr, " Experimental options:\n");
3603 fprintf (stderr, " save-target-ratio=R (where R must be between %.2f - %.2f).\n", SGEN_MIN_SAVE_TARGET_RATIO, SGEN_MAX_SAVE_TARGET_RATIO);
3604 fprintf (stderr, " default-allowance-ratio=R (where R must be between %.2f - %.2f).\n", SGEN_MIN_ALLOWANCE_NURSERY_SIZE_RATIO, SGEN_MAX_ALLOWANCE_NURSERY_SIZE_RATIO);
3605 fprintf (stderr, "\n");
3607 usage_printed = TRUE;
3609 g_strfreev (opts);
3612 if (params_opts)
3613 g_free (params_opts);
3615 alloc_nursery (dynamic_nursery, min_nursery_size, max_nursery_size);
3617 sgen_pinning_init ();
3618 sgen_cement_init (cement_enabled);
3620 if ((env = g_getenv (MONO_GC_DEBUG_NAME)) || gc_debug_options) {
3621 debug_opts = g_strdup_printf ("%s,%s", gc_debug_options ? gc_debug_options : "", env ? env : "");
3622 g_free (env);
3625 if (debug_opts) {
3626 gboolean usage_printed = FALSE;
3628 opts = g_strsplit (debug_opts, ",", -1);
3629 for (ptr = opts; ptr && *ptr; ptr ++) {
3630 char *opt = *ptr;
3631 if (!strcmp (opt, ""))
3632 continue;
3633 if (opt [0] >= '0' && opt [0] <= '9') {
3634 sgen_gc_debug_level = atoi (opt);
3635 opt++;
3636 if (opt [0] == ':')
3637 opt++;
3638 if (opt [0]) {
3639 char *rf = g_strdup_printf ("%s.%d", opt, mono_process_current_pid ());
3640 sgen_gc_debug_file = fopen (rf, "wb");
3641 if (!sgen_gc_debug_file)
3642 sgen_gc_debug_file = stderr;
3643 g_free (rf);
3645 } else if (!strcmp (opt, "print-allowance")) {
3646 debug_print_allowance = TRUE;
3647 } else if (!strcmp (opt, "print-pinning")) {
3648 sgen_pin_stats_enable ();
3649 } else if (!strcmp (opt, "print-gchandles")) {
3650 sgen_gchandle_stats_enable ();
3651 } else if (!strcmp (opt, "verify-before-allocs")) {
3652 sgen_verify_before_allocs = 1;
3653 sgen_has_per_allocation_action = TRUE;
3654 } else if (g_str_has_prefix (opt, "max-valloc-size=")) {
3655 size_t max_valloc_size;
3656 char *arg = strchr (opt, '=') + 1;
3657 if (*opt && mono_gc_parse_environment_string_extract_number (arg, &max_valloc_size)) {
3658 mono_valloc_set_limit (max_valloc_size);
3659 } else {
3660 sgen_env_var_error (MONO_GC_DEBUG_NAME, NULL, "`max-valloc-size` must be an integer.");
3662 continue;
3663 } else if (g_str_has_prefix (opt, "verify-before-allocs=")) {
3664 char *arg = strchr (opt, '=') + 1;
3665 sgen_verify_before_allocs = atoi (arg);
3666 sgen_has_per_allocation_action = TRUE;
3667 } else if (!strcmp (opt, "collect-before-allocs")) {
3668 sgen_collect_before_allocs = 1;
3669 sgen_has_per_allocation_action = TRUE;
3670 } else if (g_str_has_prefix (opt, "collect-before-allocs=")) {
3671 char *arg = strchr (opt, '=') + 1;
3672 sgen_has_per_allocation_action = TRUE;
3673 sgen_collect_before_allocs = atoi (arg);
3674 } else if (!strcmp (opt, "verify-before-collections")) {
3675 whole_heap_check_before_collection = TRUE;
3676 } else if (!strcmp (opt, "check-remset-consistency")) {
3677 remset_consistency_checks = TRUE;
3678 sgen_nursery_clear_policy = CLEAR_AT_GC;
3679 } else if (!strcmp (opt, "mod-union-consistency-check")) {
3680 if (!sgen_major_collector.is_concurrent) {
3681 sgen_env_var_error (MONO_GC_DEBUG_NAME, "Ignoring.", "`mod-union-consistency-check` only works with concurrent major collector.");
3682 continue;
3684 mod_union_consistency_check = TRUE;
3685 } else if (!strcmp (opt, "check-mark-bits")) {
3686 check_mark_bits_after_major_collection = TRUE;
3687 } else if (!strcmp (opt, "check-nursery-untag")) {
3688 check_nursery_objects_untag = TRUE;
3689 } else if (!strcmp (opt, "clear-at-gc")) {
3690 sgen_nursery_clear_policy = CLEAR_AT_GC;
3691 } else if (!strcmp (opt, "clear-nursery-at-gc")) {
3692 sgen_nursery_clear_policy = CLEAR_AT_GC;
3693 } else if (!strcmp (opt, "clear-at-tlab-creation")) {
3694 sgen_nursery_clear_policy = CLEAR_AT_TLAB_CREATION;
3695 } else if (!strcmp (opt, "debug-clear-at-tlab-creation")) {
3696 sgen_nursery_clear_policy = CLEAR_AT_TLAB_CREATION_DEBUG;
3697 } else if (!strcmp (opt, "check-scan-starts")) {
3698 do_scan_starts_check = TRUE;
3699 } else if (!strcmp (opt, "verify-nursery-at-minor-gc")) {
3700 do_verify_nursery = TRUE;
3701 } else if (!strcmp (opt, "check-concurrent")) {
3702 if (!sgen_major_collector.is_concurrent) {
3703 sgen_env_var_error (MONO_GC_DEBUG_NAME, "Ignoring.", "`check-concurrent` only works with concurrent major collectors.");
3704 continue;
3706 sgen_nursery_clear_policy = CLEAR_AT_GC;
3707 do_concurrent_checks = TRUE;
3708 } else if (!strcmp (opt, "dump-nursery-at-minor-gc")) {
3709 do_dump_nursery_content = TRUE;
3710 } else if (!strcmp (opt, "disable-minor")) {
3711 disable_minor_collections = TRUE;
3712 } else if (!strcmp (opt, "disable-major")) {
3713 disable_major_collections = TRUE;
3714 } else if (g_str_has_prefix (opt, "heap-dump=")) {
3715 char *filename = strchr (opt, '=') + 1;
3716 sgen_nursery_clear_policy = CLEAR_AT_GC;
3717 sgen_debug_enable_heap_dump (filename);
3718 } else if (g_str_has_prefix (opt, "binary-protocol=")) {
3719 char *filename = strchr (opt, '=') + 1;
3720 char *colon = strrchr (filename, ':');
3721 size_t limit = 0;
3722 if (colon) {
3723 if (!mono_gc_parse_environment_string_extract_number (colon + 1, &limit)) {
3724 sgen_env_var_error (MONO_GC_DEBUG_NAME, "Ignoring limit.", "Binary protocol file size limit must be an integer.");
3725 limit = -1;
3727 *colon = '\0';
3729 sgen_binary_protocol_init (filename, (gint64)limit);
3730 } else if (!strcmp (opt, "nursery-canaries")) {
3731 do_verify_nursery = TRUE;
3732 enable_nursery_canaries = TRUE;
3733 } else if (!sgen_client_handle_gc_debug (opt)) {
3734 sgen_env_var_error (MONO_GC_DEBUG_NAME, "Ignoring.", "Unknown option `%s`.", opt);
3736 if (usage_printed)
3737 continue;
3739 fprintf (stderr, "\n%s must be of the format [<l>[:<filename>]|<option>]+ where <l> is a debug level 0-9.\n", MONO_GC_DEBUG_NAME);
3740 fprintf (stderr, "Valid <option>s are:\n");
3741 fprintf (stderr, " collect-before-allocs[=<n>]\n");
3742 fprintf (stderr, " verify-before-allocs[=<n>]\n");
3743 fprintf (stderr, " max-valloc-size=N (where N is an integer, possibly with a k, m or a g suffix)\n");
3744 fprintf (stderr, " check-remset-consistency\n");
3745 fprintf (stderr, " check-mark-bits\n");
3746 fprintf (stderr, " check-nursery-untag\n");
3747 fprintf (stderr, " verify-before-collections\n");
3748 fprintf (stderr, " verify-nursery-at-minor-gc\n");
3749 fprintf (stderr, " dump-nursery-at-minor-gc\n");
3750 fprintf (stderr, " disable-minor\n");
3751 fprintf (stderr, " disable-major\n");
3752 fprintf (stderr, " check-concurrent\n");
3753 fprintf (stderr, " clear-[nursery-]at-gc\n");
3754 fprintf (stderr, " clear-at-tlab-creation\n");
3755 fprintf (stderr, " debug-clear-at-tlab-creation\n");
3756 fprintf (stderr, " check-scan-starts\n");
3757 fprintf (stderr, " print-allowance\n");
3758 fprintf (stderr, " print-pinning\n");
3759 fprintf (stderr, " print-gchandles\n");
3760 fprintf (stderr, " heap-dump=<filename>\n");
3761 fprintf (stderr, " binary-protocol=<filename>[:<file-size-limit>]\n");
3762 fprintf (stderr, " nursery-canaries\n");
3763 sgen_client_print_gc_debug_usage ();
3764 fprintf (stderr, "\n");
3766 usage_printed = TRUE;
3769 g_strfreev (opts);
3772 if (debug_opts)
3773 g_free (debug_opts);
3775 if (check_mark_bits_after_major_collection)
3776 sgen_nursery_clear_policy = CLEAR_AT_GC;
3778 if (sgen_major_collector.post_param_init)
3779 sgen_major_collector.post_param_init (&sgen_major_collector);
3781 sgen_thread_pool_start ();
3783 sgen_memgov_init (max_heap, soft_limit, debug_print_allowance, allowance_ratio, save_target);
3785 memset (&remset, 0, sizeof (remset));
3787 sgen_card_table_init (&remset);
3789 sgen_register_root (NULL, 0, sgen_make_user_root_descriptor (sgen_mark_normal_gc_handles), ROOT_TYPE_NORMAL, MONO_ROOT_SOURCE_GC_HANDLE, NULL, "GC Handles (SGen, Normal)");
3791 gc_initialized = 1;
3793 sgen_init_bridge ();
3796 gboolean
3797 sgen_gc_initialized ()
3799 return gc_initialized > 0;
3802 NurseryClearPolicy
3803 sgen_get_nursery_clear_policy (void)
3805 return sgen_nursery_clear_policy;
3808 void
3809 sgen_gc_lock (void)
3811 mono_coop_mutex_lock (&sgen_gc_mutex);
3814 void
3815 sgen_gc_unlock (void)
3817 mono_coop_mutex_unlock (&sgen_gc_mutex);
3820 void
3821 sgen_major_collector_iterate_live_block_ranges (sgen_cardtable_block_callback callback)
3823 sgen_major_collector.iterate_live_block_ranges (callback);
3826 void
3827 sgen_major_collector_iterate_block_ranges (sgen_cardtable_block_callback callback)
3829 sgen_major_collector.iterate_block_ranges (callback);
3832 SgenMajorCollector*
3833 sgen_get_major_collector (void)
3835 return &sgen_major_collector;
3838 SgenMinorCollector*
3839 sgen_get_minor_collector (void)
3841 return &sgen_minor_collector;
3844 SgenRememberedSet*
3845 sgen_get_remset (void)
3847 return &remset;
3850 static void
3851 count_cards (long long *major_total, long long *major_marked, long long *los_total, long long *los_marked)
3853 sgen_get_major_collector ()->count_cards (major_total, major_marked);
3854 sgen_los_count_cards (los_total, los_marked);
3857 static gboolean world_is_stopped = FALSE;
3859 /* LOCKING: assumes the GC lock is held */
3860 void
3861 sgen_stop_world (int generation, gboolean serial_collection)
3863 long long major_total = -1, major_marked = -1, los_total = -1, los_marked = -1;
3865 SGEN_ASSERT (0, !world_is_stopped, "Why are we stopping a stopped world?");
3867 sgen_binary_protocol_world_stopping (generation, sgen_timestamp (), (gpointer) (gsize) mono_native_thread_id_get ());
3869 sgen_client_stop_world (generation, serial_collection);
3871 world_is_stopped = TRUE;
3873 if (sgen_binary_protocol_is_heavy_enabled ())
3874 count_cards (&major_total, &major_marked, &los_total, &los_marked);
3875 sgen_binary_protocol_world_stopped (generation, sgen_timestamp (), major_total, major_marked, los_total, los_marked);
3878 /* LOCKING: assumes the GC lock is held */
3879 void
3880 sgen_restart_world (int generation, gboolean serial_collection)
3882 long long major_total = -1, major_marked = -1, los_total = -1, los_marked = -1;
3883 gint64 stw_time;
3885 SGEN_ASSERT (0, world_is_stopped, "Why are we restarting a running world?");
3887 if (sgen_binary_protocol_is_heavy_enabled ())
3888 count_cards (&major_total, &major_marked, &los_total, &los_marked);
3889 sgen_binary_protocol_world_restarting (generation, sgen_timestamp (), major_total, major_marked, los_total, los_marked);
3891 world_is_stopped = FALSE;
3893 sgen_client_restart_world (generation, serial_collection, &stw_time);
3895 sgen_binary_protocol_world_restarted (generation, sgen_timestamp ());
3897 if (sgen_client_bridge_need_processing ())
3898 sgen_client_bridge_processing_finish (generation);
3900 sgen_memgov_collection_end (generation, stw_time);
3903 gboolean
3904 sgen_is_world_stopped (void)
3906 return world_is_stopped;
3909 void
3910 sgen_check_whole_heap_stw (void)
3912 sgen_stop_world (0, FALSE);
3913 sgen_clear_nursery_fragments ();
3914 sgen_check_whole_heap (TRUE);
3915 sgen_restart_world (0, FALSE);
3918 gint64
3919 sgen_timestamp (void)
3921 SGEN_TV_DECLARE (timestamp);
3922 SGEN_TV_GETTIME (timestamp);
3923 return SGEN_TV_ELAPSED (sgen_init_timestamp, timestamp);
3926 void
3927 sgen_check_canary_for_object (gpointer addr)
3929 if (sgen_nursery_canaries_enabled ()) {
3930 guint size = sgen_safe_object_get_size_unaligned ((GCObject *) (addr));
3931 char* canary_ptr = (char*) (addr) + size;
3932 if (!CANARY_VALID(canary_ptr)) {
3933 char *window_start, *window_end;
3934 window_start = (char*)(addr) - 128;
3935 if (!sgen_ptr_in_nursery (window_start))
3936 window_start = sgen_get_nursery_start ();
3937 window_end = (char*)(addr) + 128;
3938 if (!sgen_ptr_in_nursery (window_end))
3939 window_end = sgen_get_nursery_end ();
3940 fprintf (stderr, "\nCANARY ERROR - Type:%s Size:%d Address:%p Data:\n", sgen_client_vtable_get_name (SGEN_LOAD_VTABLE ((addr))), size, (char*) addr);
3941 fwrite (addr, sizeof (char), size, stderr);
3942 fprintf (stderr, "\nCanary zone (next 12 chars):\n");
3943 fwrite (canary_ptr, sizeof (char), 12, stderr);
3944 fprintf (stderr, "\nOriginal canary string:\n");
3945 fwrite (CANARY_STRING, sizeof (char), 8, stderr);
3946 for (int x = -8; x <= 8; x++) {
3947 if (canary_ptr + x < (char*) addr)
3948 continue;
3949 if (CANARY_VALID(canary_ptr +x))
3950 fprintf (stderr, "\nCANARY ERROR - canary found at offset %d\n", x);
3952 fprintf (stderr, "\nSurrounding nursery (%p - %p):\n", window_start, window_end);
3953 fwrite (window_start, sizeof (char), window_end - window_start, stderr);
3958 #endif /* HAVE_SGEN_GC */