X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mono%2Fmetadata%2Fsgen-gc.c;h=3607182b8366f0637c4eaf21e098707866d401c2;hb=7f8a68150cd16aae4e59e49e1524c242da9cdad2;hp=bf4ef445b6e51a40ff61e6994ddd5121299098df;hpb=b87c7e29df7d04e3199e224c8b8e9a41292cec1b;p=mono.git diff --git a/mono/metadata/sgen-gc.c b/mono/metadata/sgen-gc.c index bf4ef445b6e..3607182b836 100644 --- a/mono/metadata/sgen-gc.c +++ b/mono/metadata/sgen-gc.c @@ -99,7 +99,7 @@ Multi-dim arrays have the same issue for rank == 1 for the bounds data. *) implement a card table as the write barrier instead of remembered sets? *) some sort of blacklist support? - *) fin_ready_list is part of the root set, too + *) fin_ready_list and critical_fin_list are part of the root set, too *) consider lowering the large object min size to 16/32KB or so and benchmark *) once mark-compact is implemented we could still keep the copying collector for the old generation and use it if we think @@ -528,13 +528,25 @@ typedef struct _FinalizeEntry FinalizeEntry; struct _FinalizeEntry { FinalizeEntry *next; void *object; - void *data; /* can be a disappearing link or the data for the finalizer */ - /* Note we could use just one pointer if we don't support multiple callbacks - * for finalizers and per-finalizer data and if we store the obj pointers - * in the link like libgc does - */ }; +typedef struct _DisappearingLink DisappearingLink; +struct _DisappearingLink { + DisappearingLink *next; + void **link; +}; + +/* + * The link pointer is hidden by negating each bit. We use the lowest + * bit of the link (before negation) to store whether it needs + * resurrection tracking. + */ +#define HIDE_POINTER(p,t) ((gpointer)(~((gulong)(p)|((t)?1:0)))) +#define REVEAL_POINTER(p) ((gpointer)((~(gulong)(p))&~3L)) + +#define DISLINK_OBJECT(d) (REVEAL_POINTER (*(d)->link)) +#define DISLINK_TRACK(d) ((~(gulong)(*(d)->link)) & 1) + /* * The finalizable hash has the object as the key, the * disappearing_link hash, has the link address as key. @@ -542,8 +554,8 @@ struct _FinalizeEntry { static FinalizeEntry **finalizable_hash = NULL; /* objects that are ready to be finalized */ static FinalizeEntry *fin_ready_list = NULL; -/* disappearing links use the same structure but a different list */ -static FinalizeEntry **disappearing_link_hash = NULL; +static FinalizeEntry *critical_fin_list = NULL; +static DisappearingLink **disappearing_link_hash = NULL; static mword disappearing_link_hash_size = 0; static mword finalizable_hash_size = 0; @@ -662,6 +674,9 @@ static GCMemSection *to_space_section = NULL; /* objects bigger then this go into the large object space */ #define MAX_SMALL_OBJ_SIZE 0xffff +/* Functions supplied by the runtime to be called by the GC */ +static MonoGCCallbacks gc_callbacks; + /* * ###################################################################### * ######## Macros and function declarations. @@ -693,7 +708,7 @@ static G_GNUC_UNUSED void report_internal_mem_usage (void); static int stop_world (void); static int restart_world (void); -static void pin_thread_data (void *start_nursery, void *end_nursery); +static void scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise); static void scan_from_remsets (void *start_nursery, void *end_nursery); static void find_pinning_ref_from_thread (char *obj, size_t size); static void update_current_thread_stack (void *start); @@ -861,7 +876,7 @@ mono_gc_make_descr_for_object (gsize *bitmap, int numbits, size_t obj_size) stored_size += ALLOC_ALIGN - 1; stored_size &= ~(ALLOC_ALIGN - 1); for (i = 0; i < numbits; ++i) { - if (bitmap [i / GC_BITS_PER_WORD] & (1 << (i % GC_BITS_PER_WORD))) { + if (bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) { if (first_set < 0) first_set = i; last_set = i; @@ -907,7 +922,7 @@ mono_gc_make_descr_for_array (int vector, gsize *elem_bitmap, int numbits, size_ int first_set = -1, num_set = 0, last_set = -1, i; mword desc = vector? DESC_TYPE_VECTOR: DESC_TYPE_ARRAY; for (i = 0; i < numbits; ++i) { - if (elem_bitmap [i / GC_BITS_PER_WORD] & (1 << (i % GC_BITS_PER_WORD))) { + if (elem_bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) { if (first_set < 0) first_set = i; last_set = i; @@ -935,6 +950,41 @@ mono_gc_make_descr_for_array (int vector, gsize *elem_bitmap, int numbits, size_ return (void*) desc; } +/* Return the bitmap encoded by a descriptor */ +gsize* +mono_gc_get_bitmap_for_descr (void *descr, int *numbits) +{ + mword d = (mword)descr; + gsize *bitmap; + + switch (d & 0x7) { + case DESC_TYPE_RUN_LENGTH: { + int first_set = (d >> 16) & 0xff; + int num_set = (d >> 16) & 0xff; + int i; + + bitmap = g_new0 (gsize, (first_set + num_set + 7) / 8); + + for (i = first_set; i < first_set + num_set; ++i) + bitmap [i / GC_BITS_PER_WORD] |= ((gsize)1 << (i % GC_BITS_PER_WORD)); + + *numbits = first_set + num_set; + + return bitmap; + } + case DESC_TYPE_SMALL_BITMAP: + bitmap = g_new0 (gsize, 1); + + bitmap [0] = (d >> SMALL_BITMAP_SHIFT) << OBJECT_HEADER_WORDS; + + *numbits = GC_BITS_PER_WORD; + + return bitmap; + default: + g_assert_not_reached (); + } +} + /* helper macros to scan and traverse objects, macros because we resue them in many functions */ #define STRING_SIZE(size,str) do { \ (size) = sizeof (MonoString) + 2 * (mono_string_length ((MonoString*)(str)) + 1); \ @@ -1361,7 +1411,7 @@ add_to_global_remset (gpointer ptr, gboolean root) global_remset = rs; if (root) { *(global_remset->store_next++) = (mword)ptr | REMSET_OTHER; - *(global_remset->store_next++) = (mword)REMSET_LOCATION; + *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION; } else { *(global_remset->store_next++) = (mword)ptr; } @@ -1945,7 +1995,7 @@ pin_from_roots (void *start_nursery, void *end_nursery) * *) the _last_ managed stack frame * *) pointers slots in managed frames */ - pin_thread_data (start_nursery, end_nursery); + scan_thread_data (start_nursery, end_nursery, FALSE); } /* Copy function called from user defined mark functions */ @@ -2098,6 +2148,18 @@ alloc_nursery (void) /* FIXME: frag here is lost */ } +static void +scan_finalizer_entries (FinalizeEntry *list, char *start, char *end) { + FinalizeEntry *fin; + + for (fin = list; fin; fin = fin->next) { + if (!fin->object) + continue; + DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object))); + fin->object = copy_object (fin->object, start, end); + } +} + /* * Update roots in the old generation. Since we currently don't have the * info from the write barriers, we just scan all the objects. @@ -2106,7 +2168,6 @@ static G_GNUC_UNUSED void scan_old_generation (char *start, char* end) { GCMemSection *section; - FinalizeEntry *fin; LOSObject *big_object; char *p; @@ -2131,10 +2192,8 @@ scan_old_generation (char *start, char* end) scan_object (big_object->data, start, end); } /* scan the list of objects ready for finalization */ - for (fin = fin_ready_list; fin; fin = fin->next) { - DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object))); - fin->object = copy_object (fin->object, start, end); - } + scan_finalizer_entries (fin_ready_list, start, end); + scan_finalizer_entries (critical_fin_list, start, end); } static mword fragment_total = 0; @@ -2457,6 +2516,7 @@ collect_nursery (size_t requested_size) } /* registered roots, this includes static fields */ scan_from_registered_roots (nursery_start, nursery_next, ROOT_TYPE_NORMAL); + scan_thread_data (nursery_start, nursery_next, TRUE); /* alloc_pinned objects */ scan_from_pinned_objects (nursery_start, nursery_next); TV_GETTIME (btv); @@ -2478,7 +2538,7 @@ collect_nursery (size_t requested_size) /* prepare the pin queue for the next collection */ last_num_pinned = next_pin_slot; next_pin_slot = 0; - if (fin_ready_list) { + if (fin_ready_list || critical_fin_list) { DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers)); mono_gc_finalize_notify (); } @@ -2491,7 +2551,6 @@ major_collection (void) LOSObject *bigobj, *prevbo; int i; PinnedChunk *chunk; - FinalizeEntry *fin; Fragment *frag; int count; TV_DECLARE (all_atv); @@ -2601,13 +2660,13 @@ major_collection (void) /* registered roots, this includes static fields */ scan_from_registered_roots (heap_start, heap_end, ROOT_TYPE_NORMAL); scan_from_registered_roots (heap_start, heap_end, ROOT_TYPE_WBARRIER); + /* Threads */ + scan_thread_data (heap_start, heap_end, TRUE); /* alloc_pinned objects */ scan_from_pinned_objects (heap_start, heap_end); /* scan the list of objects ready for finalization */ - for (fin = fin_ready_list; fin; fin = fin->next) { - DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object))); - fin->object = copy_object (fin->object, heap_start, heap_end); - } + scan_finalizer_entries (fin_ready_list, heap_start, heap_end); + scan_finalizer_entries (critical_fin_list, heap_start, heap_end); TV_GETTIME (atv); DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (btv, atv))); @@ -2681,7 +2740,7 @@ major_collection (void) mono_stats.major_gc_time_usecs += TV_ELAPSED (all_atv, all_btv); /* prepare the pin queue for the next collection */ next_pin_slot = 0; - if (fin_ready_list) { + if (fin_ready_list || critical_fin_list) { DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers)); mono_gc_finalize_notify (); } @@ -3511,6 +3570,32 @@ mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size) */ #define object_is_fin_ready(obj) (!object_is_pinned (obj) && !object_is_forwarded (obj)) +static gboolean +is_critical_finalizer (FinalizeEntry *entry) +{ + MonoObject *obj; + MonoClass *class; + + if (!mono_defaults.critical_finalizer_object) + return FALSE; + + obj = entry->object; + class = ((MonoVTable*)LOAD_VTABLE (obj))->klass; + + return mono_class_has_parent (class, mono_defaults.critical_finalizer_object); +} + +static void +queue_finalization_entry (FinalizeEntry *entry) { + if (is_critical_finalizer (entry)) { + entry->next = critical_fin_list; + critical_fin_list = entry; + } else { + entry->next = fin_ready_list; + fin_ready_list = entry; + } +} + static void finalize_in_range (char *start, char *end) { @@ -3533,8 +3618,7 @@ finalize_in_range (char *start, char *end) next = entry->next; num_ready_finalizers++; num_registered_finalizers--; - entry->next = fin_ready_list; - fin_ready_list = entry; + queue_finalization_entry (entry); /* Make it survive */ from = entry->object; entry->object = copy_object (entry->object, start, end); @@ -3556,36 +3640,41 @@ finalize_in_range (char *start, char *end) static void null_link_in_range (char *start, char *end) { - FinalizeEntry *entry, *prev; + DisappearingLink *entry, *prev; int i; for (i = 0; i < disappearing_link_hash_size; ++i) { prev = NULL; for (entry = disappearing_link_hash [i]; entry;) { - if ((char*)entry->object >= start && (char*)entry->object < end && ((char*)entry->object < to_space || (char*)entry->object >= to_space_end)) { - if (object_is_fin_ready (entry->object)) { - void **p = entry->data; - FinalizeEntry *old; + char *object = DISLINK_OBJECT (entry); + if (object >= start && object < end && (object < to_space || object >= to_space_end)) { + if (!DISLINK_TRACK (entry) && object_is_fin_ready (object)) { + void **p = entry->link; + DisappearingLink *old; *p = NULL; /* remove from list */ if (prev) prev->next = entry->next; else disappearing_link_hash [i] = entry->next; - DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, entry->object)); + DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object)); old = entry->next; free_internal_mem (entry); entry = old; num_disappearing_links--; continue; } else { - void **link; /* update pointer if it's moved * FIXME: what if an object is moved earlier? */ - entry->object = copy_object (entry->object, start, end); - DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->data, entry->object)); - link = entry->data; - *link = entry->object; + /* We set the track + * resurrection bit to FALSE + * here so that the object can + * be collected in the next + * cycle (i.e. after it was + * finalized). + */ + *entry->link = HIDE_POINTER (copy_object (object, start, end), FALSE); + DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry))); } } prev = entry; @@ -3675,6 +3764,7 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data) unsigned int hash; if (no_finalize) return; + g_assert (user_data == NULL || user_data == mono_gc_run_finalize); hash = mono_object_hash (obj); LOCK_GC; if (num_registered_finalizers >= finalizable_hash_size * 2) @@ -3683,9 +3773,7 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data) prev = NULL; for (entry = finalizable_hash [hash]; entry; entry = entry->next) { if (entry->object == obj) { - if (user_data) { - entry->data = user_data; - } else { + if (!user_data) { /* remove from the list */ if (prev) prev->next = entry->next; @@ -3707,7 +3795,6 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data) } entry = get_internal_mem (sizeof (FinalizeEntry)); entry->object = obj; - entry->data = user_data; entry->next = finalizable_hash [hash]; finalizable_hash [hash] = entry; num_registered_finalizers++; @@ -3720,14 +3807,14 @@ rehash_dislink (void) { int i; unsigned int hash; - FinalizeEntry **new_hash; - FinalizeEntry *entry, *next; + DisappearingLink **new_hash; + DisappearingLink *entry, *next; int new_size = g_spaced_primes_closest (num_disappearing_links); - new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*)); + new_hash = get_internal_mem (new_size * sizeof (DisappearingLink*)); for (i = 0; i < disappearing_link_hash_size; ++i) { for (entry = disappearing_link_hash [i]; entry; entry = next) { - hash = mono_aligned_addr_hash (entry->data) % new_size; + hash = mono_aligned_addr_hash (entry->link) % new_size; next = entry->next; entry->next = new_hash [hash]; new_hash [hash] = entry; @@ -3739,9 +3826,9 @@ rehash_dislink (void) } static void -mono_gc_register_disappearing_link (MonoObject *obj, void *link) +mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track) { - FinalizeEntry *entry, *prev; + DisappearingLink *entry, *prev; unsigned int hash; LOCK_GC; @@ -3753,7 +3840,7 @@ mono_gc_register_disappearing_link (MonoObject *obj, void *link) prev = NULL; for (; entry; entry = entry->next) { /* link already added */ - if (link == entry->data) { + if (link == entry->link) { /* NULL obj means remove */ if (obj == NULL) { if (prev) @@ -3763,17 +3850,18 @@ mono_gc_register_disappearing_link (MonoObject *obj, void *link) num_disappearing_links--; DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d)\n", entry, num_disappearing_links)); free_internal_mem (entry); + *link = NULL; } else { - entry->object = obj; /* we allow the change of object */ + *link = HIDE_POINTER (obj, track); /* we allow the change of object */ } UNLOCK_GC; return; } prev = entry; } - entry = get_internal_mem (sizeof (FinalizeEntry)); - entry->object = obj; - entry->data = link; + entry = get_internal_mem (sizeof (DisappearingLink)); + *link = HIDE_POINTER (obj, track); + entry->link = link; entry->next = disappearing_link_hash [hash]; disappearing_link_hash [hash] = entry; num_disappearing_links++; @@ -3784,38 +3872,70 @@ mono_gc_register_disappearing_link (MonoObject *obj, void *link) int mono_gc_invoke_finalizers (void) { - FinalizeEntry *entry; + FinalizeEntry *entry = NULL; + gboolean entry_is_critical; int count = 0; void *obj; /* FIXME: batch to reduce lock contention */ - while (fin_ready_list) { + while (fin_ready_list || critical_fin_list) { LOCK_GC; - entry = fin_ready_list; + + if (entry) { + FinalizeEntry **list = entry_is_critical ? &critical_fin_list : &fin_ready_list; + + /* We have finalized entry in the last + interation, now we need to remove it from + the list. */ + if (*list == entry) + *list = entry->next; + else { + FinalizeEntry *e = *list; + while (e->next != entry) + e = e->next; + e->next = entry->next; + } + free_internal_mem (entry); + entry = NULL; + } + + /* Now look for the first non-null entry. */ + for (entry = fin_ready_list; entry && !entry->object; entry = entry->next) + ; if (entry) { - fin_ready_list = entry->next; + entry_is_critical = FALSE; + } else { + entry_is_critical = TRUE; + for (entry = critical_fin_list; entry && !entry->object; entry = entry->next) + ; + } + + if (entry) { + g_assert (entry->object); num_ready_finalizers--; obj = entry->object; + entry->object = NULL; DEBUG (7, fprintf (gc_debug_file, "Finalizing object %p (%s)\n", obj, safe_name (obj))); } + UNLOCK_GC; - if (entry) { - void (*callback)(void *, void*) = entry->data; - entry->next = NULL; - obj = entry->object; - count++; - /* the object is on the stack so it is pinned */ - /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/ - callback (obj, NULL); - free_internal_mem (entry); - } + + if (!entry) + break; + + g_assert (entry->object == NULL); + count++; + /* the object is on the stack so it is pinned */ + /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/ + mono_gc_run_finalize (obj, NULL); } + g_assert (!entry); return count; } gboolean mono_gc_pending_finalizers (void) { - return fin_ready_list != NULL; + return fin_ready_list || critical_fin_list; } /* Negative value to remove */ @@ -3987,6 +4107,7 @@ struct _SgenThreadInfo { char **tlab_temp_end_addr; char **tlab_real_end_addr; RememberedSet *remset; + gpointer runtime_data; }; /* FIXME: handle large/small config */ @@ -4025,6 +4146,8 @@ update_current_thread_stack (void *start) SgenThreadInfo *info = thread_info_lookup (ARCH_GET_THREAD ()); info->stack_start = align_pointer (&ptr); ARCH_STORE_REGS (ptr); + if (gc_callbacks.thread_suspend_func) + gc_callbacks.thread_suspend_func (info->runtime_data, NULL); } static const char* @@ -4078,7 +4201,7 @@ thread_handshake (int signum) /* LOCKING: assumes the GC lock is held (by the stopping thread) */ static void -suspend_handler (int sig) +suspend_handler (int sig, siginfo_t *siginfo, void *context) { SgenThreadInfo *info; pthread_t id; @@ -4102,6 +4225,10 @@ suspend_handler (int sig) */ info->stack_start = align_pointer (&id); + /* Notify the JIT */ + if (gc_callbacks.thread_suspend_func) + gc_callbacks.thread_suspend_func (info->runtime_data, context); + /* notify the waiting thread */ sem_post (&suspend_ack_semaphore); info->stop_count = stop_count; @@ -4165,15 +4292,39 @@ restart_world (void) #endif /* USE_SIGNAL_BASED_START_STOP_WORLD */ +void +mono_gc_set_gc_callbacks (MonoGCCallbacks *callbacks) +{ + gc_callbacks = *callbacks; +} + +/* Variables holding start/end nursery so it won't have to be passed at every call */ +static void *scan_area_arg_start, *scan_area_arg_end; + +void +mono_gc_conservatively_scan_area (void *start, void *end) +{ + conservatively_pin_objects_from (start, end, scan_area_arg_start, scan_area_arg_end); +} + +void* +mono_gc_scan_object (void *obj) +{ + return copy_object (obj, scan_area_arg_start, scan_area_arg_end); +} + /* - * Identify objects pinned in a thread stack and its registers. + * Mark from thread stacks and registers. */ static void -pin_thread_data (void *start_nursery, void *end_nursery) +scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise) { int i; SgenThreadInfo *info; + scan_area_arg_start = start_nursery; + scan_area_arg_end = end_nursery; + for (i = 0; i < THREAD_HASH_SIZE; ++i) { for (info = thread_table [i]; info; info = info->next) { if (info->skip) { @@ -4181,11 +4332,15 @@ pin_thread_data (void *start_nursery, void *end_nursery) continue; } DEBUG (2, fprintf (gc_debug_file, "Scanning thread %p, range: %p-%p, size: %zd, pinned=%d\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start, next_pin_slot)); - conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery); + if (gc_callbacks.thread_mark_func) + gc_callbacks.thread_mark_func (info->runtime_data, info->stack_start, info->stack_end, precise); + else if (!precise) + conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery); } } DEBUG (2, fprintf (gc_debug_file, "Scanning current thread registers, pinned=%d\n", next_pin_slot)); - conservatively_pin_objects_from ((void*)cur_thread_regs, (void*)(cur_thread_regs + ARCH_NUM_REGS), start_nursery, end_nursery); + if (!precise) + conservatively_pin_objects_from ((void*)cur_thread_regs, (void*)(cur_thread_regs + ARCH_NUM_REGS), start_nursery, end_nursery); } static void @@ -4490,6 +4645,10 @@ gc_register_current_thread (void *addr) remembered_set = info->remset = alloc_remset (DEFAULT_REMSET_SIZE, info); pthread_setspecific (remembered_set_key, remembered_set); DEBUG (3, fprintf (gc_debug_file, "registered thread %p (%p) (hash: %d)\n", info, (gpointer)info->id, hash)); + + if (gc_callbacks.thread_attach_func) + info->runtime_data = gc_callbacks.thread_attach_func (); + return info; } @@ -5271,23 +5430,23 @@ mono_gc_enable_events (void) } void -mono_gc_weak_link_add (void **link_addr, MonoObject *obj) +mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track) { - mono_gc_register_disappearing_link (obj, link_addr); - *link_addr = obj; + mono_gc_register_disappearing_link (obj, link_addr, track); } void mono_gc_weak_link_remove (void **link_addr) { - mono_gc_register_disappearing_link (NULL, link_addr); - *link_addr = NULL; + mono_gc_register_disappearing_link (NULL, link_addr, FALSE); } MonoObject* mono_gc_weak_link_get (void **link_addr) { - return *link_addr; + if (!*link_addr) + return NULL; + return (MonoObject*) REVEAL_POINTER (*link_addr); } void* @@ -5379,10 +5538,12 @@ mono_gc_base_init (void) collect_before_allocs = TRUE; } else if (!strcmp (opt, "check-at-minor-collections")) { consistency_check_at_minor_collection = TRUE; + } else if (!strcmp (opt, "clear-at-gc")) { + nursery_clear_policy = CLEAR_AT_GC; } else { fprintf (stderr, "Invalid format for the MONO_GC_DEBUG env variable: '%s'\n", env); fprintf (stderr, "The format is: MONO_GC_DEBUG=[l[:filename]|