2010-05-28 Rodrigo Kumpera <rkumpera@novell.com>
[mono.git] / mono / metadata / sgen-gc.c
index a0d75b3516fa58ebe6f9a0145fc77d3be4b6921e..1691c436563372acfe47dd352e95481e9d9e59a6 100644 (file)
     assigned TLABs and if that's more than some percentage of the
     nursery size, reduce the TLAB size.
 
+ *) Explore placing unreachable objects on unused nursery memory.
+       Instead of memset'ng a region to zero, place an int[] covering it.
+       A good place to start is add_nursery_frag. The tricky thing here is
+       placing those objects atomically outside of a collection.
+
+
  */
 #include "config.h"
 #ifdef HAVE_SGEN_GC
@@ -253,30 +259,35 @@ static gboolean do_scan_starts_check = FALSE;
 #endif
 
 #ifdef HEAVY_STATISTICS
-static long stat_objects_alloced = 0;
-static long stat_bytes_alloced = 0;
-static long stat_objects_alloced_degraded = 0;
-static long stat_bytes_alloced_degraded = 0;
-static long stat_bytes_alloced_los = 0;
-
-static long stat_copy_object_called_nursery = 0;
-static long stat_objects_copied_nursery = 0;
-static long stat_copy_object_called_major = 0;
-static long stat_objects_copied_major = 0;
-
-static long stat_scan_object_called_nursery = 0;
-static long stat_scan_object_called_major = 0;
-
-static long stat_nursery_copy_object_failed_from_space = 0;
-static long stat_nursery_copy_object_failed_forwarded = 0;
-static long stat_nursery_copy_object_failed_pinned = 0;
-
-static long stat_store_remsets = 0;
-static long stat_store_remsets_unique = 0;
-static long stat_saved_remsets_1 = 0;
-static long stat_saved_remsets_2 = 0;
-static long stat_global_remsets_added = 0;
-static long stat_global_remsets_processed = 0;
+static long long stat_objects_alloced = 0;
+static long long stat_bytes_alloced = 0;
+static long long stat_objects_alloced_degraded = 0;
+static long long stat_bytes_alloced_degraded = 0;
+static long long stat_bytes_alloced_los = 0;
+
+static long long stat_copy_object_called_nursery = 0;
+static long long stat_objects_copied_nursery = 0;
+static long long stat_copy_object_called_major = 0;
+static long long stat_objects_copied_major = 0;
+
+static long long stat_scan_object_called_nursery = 0;
+static long long stat_scan_object_called_major = 0;
+
+static long long stat_nursery_copy_object_failed_from_space = 0;
+static long long stat_nursery_copy_object_failed_forwarded = 0;
+static long long stat_nursery_copy_object_failed_pinned = 0;
+
+static long long stat_store_remsets = 0;
+static long long stat_store_remsets_unique = 0;
+static long long stat_saved_remsets_1 = 0;
+static long long stat_saved_remsets_2 = 0;
+static long long stat_global_remsets_added = 0;
+static long long stat_global_remsets_readded = 0;
+static long long stat_global_remsets_processed = 0;
+static long long stat_global_remsets_discarded = 0;
+
+static long long stat_wasted_fragments_used = 0;
+static long long stat_wasted_fragments_bytes = 0;
 
 static int stat_wbarrier_set_field = 0;
 static int stat_wbarrier_set_arrayref = 0;
@@ -288,29 +299,29 @@ static int stat_wbarrier_value_copy = 0;
 static int stat_wbarrier_object_copy = 0;
 #endif
 
-static long time_minor_pre_collection_fragment_clear = 0;
-static long time_minor_pinning = 0;
-static long time_minor_scan_remsets = 0;
-static long time_minor_scan_pinned = 0;
-static long time_minor_scan_registered_roots = 0;
-static long time_minor_scan_thread_data = 0;
-static long time_minor_finish_gray_stack = 0;
-static long time_minor_fragment_creation = 0;
-
-static long time_major_pre_collection_fragment_clear = 0;
-static long time_major_pinning = 0;
-static long time_major_scan_pinned = 0;
-static long time_major_scan_registered_roots = 0;
-static long time_major_scan_thread_data = 0;
-static long time_major_scan_alloc_pinned = 0;
-static long time_major_scan_finalized = 0;
-static long time_major_scan_big_objects = 0;
-static long time_major_finish_gray_stack = 0;
-static long time_major_sweep = 0;
-static long time_major_fragment_creation = 0;
-
-static long pinned_chunk_bytes_alloced = 0;
-static long large_internal_bytes_alloced = 0;
+static long long time_minor_pre_collection_fragment_clear = 0;
+static long long time_minor_pinning = 0;
+static long long time_minor_scan_remsets = 0;
+static long long time_minor_scan_pinned = 0;
+static long long time_minor_scan_registered_roots = 0;
+static long long time_minor_scan_thread_data = 0;
+static long long time_minor_finish_gray_stack = 0;
+static long long time_minor_fragment_creation = 0;
+
+static long long time_major_pre_collection_fragment_clear = 0;
+static long long time_major_pinning = 0;
+static long long time_major_scan_pinned = 0;
+static long long time_major_scan_registered_roots = 0;
+static long long time_major_scan_thread_data = 0;
+static long long time_major_scan_alloc_pinned = 0;
+static long long time_major_scan_finalized = 0;
+static long long time_major_scan_big_objects = 0;
+static long long time_major_finish_gray_stack = 0;
+static long long time_major_sweep = 0;
+static long long time_major_fragment_creation = 0;
+
+static long long pinned_chunk_bytes_alloced = 0;
+static long long large_internal_bytes_alloced = 0;
 
 /* Keep in sync with internal_mem_names in dump_heap()! */
 enum {
@@ -330,6 +341,7 @@ enum {
        INTERNAL_MEM_STORE_REMSET,
        INTERNAL_MEM_MS_TABLES,
        INTERNAL_MEM_MS_BLOCK_INFO,
+       INTERNAL_MEM_EPHEMERON_LINK,
        INTERNAL_MEM_MAX
 };
 
@@ -547,16 +559,10 @@ enum {
        REMSET_LOCATION, /* just a pointer to the exact location */
        REMSET_RANGE,    /* range of pointer fields */
        REMSET_OBJECT,   /* mark all the object for scanning */
-       REMSET_OTHER,    /* all others */
+       REMSET_VTYPE,    /* a valuetype array described by a gc descriptor and a count */
        REMSET_TYPE_MASK = 0x3
 };
 
-/* Subtypes of REMSET_OTHER */
-enum {
-       REMSET_VTYPE, /* a valuetype array described by a gc descriptor and a count */
-       REMSET_ROOT_LOCATION, /* a location inside a root */
-};
-
 #ifdef HAVE_KW_THREAD
 static __thread RememberedSet *remembered_set MONO_TLS_FAST;
 #endif
@@ -565,6 +571,9 @@ static RememberedSet *global_remset;
 static RememberedSet *freed_thread_remsets;
 static GenericStoreRememberedSet *generic_store_remsets = NULL;
 
+/*A two slots cache for recently inserted remsets */
+static gpointer global_remset_cache [2];
+
 /* FIXME: later choose a size that takes into account the RememberedSet struct
  * and doesn't waste any alloc paddin space.
  */
@@ -632,10 +641,10 @@ safe_object_get_size (MonoObject* o)
 {
        MonoClass *klass = ((MonoVTable*)LOAD_VTABLE (o))->klass;
        if (klass == mono_defaults.string_class) {
-               return sizeof (MonoString) + 2 * mono_string_length ((MonoString*) o) + 2;
+               return sizeof (MonoString) + 2 * mono_string_length_fast ((MonoString*) o) + 2;
        } else if (klass->rank) {
                MonoArray *array = (MonoArray*)o;
-               size_t size = sizeof (MonoArray) + klass->sizes.element_size * mono_array_length (array);
+               size_t size = sizeof (MonoArray) + klass->sizes.element_size * mono_array_length_fast (array);
                if (G_UNLIKELY (array->bounds)) {
                        size += sizeof (mono_array_size_t) - 1;
                        size &= ~(sizeof (mono_array_size_t) - 1);
@@ -734,6 +743,18 @@ struct _DisappearingLinkHashTable {
        int num_links;
 };
 
+typedef struct _EphemeronLinkNode EphemeronLinkNode;
+
+struct _EphemeronLinkNode {
+       EphemeronLinkNode *next;
+       char *array;
+};
+
+typedef struct {
+       void *key;
+       void *value;
+} Ephemeron;
+
 #define LARGE_INTERNAL_MEM_HEADER_MAGIC        0x7d289f3a
 
 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
@@ -775,6 +796,8 @@ static FinalizeEntry *critical_fin_list = NULL;
 static DisappearingLinkHashTable minor_disappearing_link_hash;
 static DisappearingLinkHashTable major_disappearing_link_hash;
 
+static EphemeronLinkNode *ephemeron_list;
+
 static int num_ready_finalizers = 0;
 static int no_finalize = 0;
 
@@ -922,6 +945,9 @@ static char *nursery_last_pinned_end = NULL;
  */
 static guint32 tlab_size = (1024 * 4);
 
+/*How much space is tolerable to be wasted from the current fragment when allocating a new TLAB*/
+#define MAX_NURSERY_TLAB_WASTE 512
+
 /* fragments that are free and ready to be used for allocation */
 static Fragment *nursery_fragments = NULL;
 /* freeelist of fragment structures */
@@ -933,7 +959,8 @@ static Fragment *fragment_freelist = NULL;
 /* Functions supplied by the runtime to be called by the GC */
 static MonoGCCallbacks gc_callbacks;
 
-#define ALLOC_ALIGN 8
+#define ALLOC_ALIGN            8
+#define ALLOC_ALIGN_BITS       3
 
 #define MOVED_OBJECTS_NUM 64
 static void *moved_objects [MOVED_OBJECTS_NUM];
@@ -975,7 +1002,7 @@ static G_GNUC_UNUSED void  report_internal_mem_usage (void);
 
 static int stop_world (void);
 static int restart_world (void);
-static void add_to_global_remset (gpointer ptr, gboolean root);
+static void add_to_global_remset (gpointer ptr);
 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 scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type);
@@ -987,6 +1014,7 @@ static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gbool
 static void null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation);
 static void null_links_for_domain (MonoDomain *domain, int generation);
 static gboolean search_fragment_for_size (size_t size);
+static int search_fragment_for_size_range (size_t desired_size, size_t minimum_size);
 static void build_nursery_fragments (int start_pin, int end_pin);
 static void clear_nursery_fragments (char *next);
 static void pin_from_roots (void *start_nursery, void *end_nursery);
@@ -1025,6 +1053,10 @@ void mono_gc_scan_for_specific_ref (MonoObject *key);
 
 static void init_stats (void);
 
+static int mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end);
+static void clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end);
+static void null_ephemerons_for_domain (MonoDomain *domain);
+
 //#define BINARY_PROTOCOL
 #include "sgen-protocol.c"
 #include "sgen-pinning.c"
@@ -1285,7 +1317,7 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
 
 /* 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)) + 2; \
+               (size) = sizeof (MonoString) + 2 * mono_string_length_fast ((MonoString*)(str)) + 2;    \
                (size) += (ALLOC_ALIGN - 1);    \
                (size) &= ~(ALLOC_ALIGN - 1);   \
        } while (0)
@@ -1380,7 +1412,7 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
                int mbwords = (*mbitmap_data++) - 1;    \
                int el_size = mono_array_element_size (((MonoObject*)(obj))->vtable->klass);    \
                char *e_start = (char*)(obj) +  G_STRUCT_OFFSET (MonoArray, vector);    \
-               char *e_end = e_start + el_size * mono_array_length ((MonoArray*)(obj));        \
+               char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj));   \
                if (0) {        \
                        MonoObject *myobj = (MonoObject*)start; \
                        g_print ("found %d at %p (0x%zx): %s.%s\n", mbwords, (obj), (vt)->desc, myobj->vtable->klass->name_space, myobj->vtable->klass->name);  \
@@ -1414,7 +1446,7 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
                        int etype = (vt)->desc & 0xc000;        \
                        if (etype == (DESC_TYPE_V_REFS << 14)) {        \
                                void **p = (void**)((char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector));        \
-                               void **end_refs = (void**)((char*)p + el_size * mono_array_length ((MonoArray*)(obj))); \
+                               void **end_refs = (void**)((char*)p + el_size * mono_array_length_fast ((MonoArray*)(obj)));    \
                                /* Note: this code can handle also arrays of struct with only references in them */     \
                                while (p < end_refs) {  \
                                        HANDLE_PTR (p, (obj));  \
@@ -1424,7 +1456,7 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
                                int offset = ((vt)->desc >> 16) & 0xff; \
                                int num_refs = ((vt)->desc >> 24) & 0xff;       \
                                char *e_start = (char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector);     \
-                               char *e_end = e_start + el_size * mono_array_length ((MonoArray*)(obj));        \
+                               char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj));   \
                                while (e_start < e_end) {       \
                                        void **p = (void**)e_start;     \
                                        int i;  \
@@ -1436,7 +1468,7 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
                                }       \
                        } else if (etype == DESC_TYPE_V_BITMAP << 14) { \
                                char *e_start = (char*)(obj) +  G_STRUCT_OFFSET (MonoArray, vector);    \
-                               char *e_end = e_start + el_size * mono_array_length ((MonoArray*)(obj));        \
+                               char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj));   \
                                while (e_start < e_end) {       \
                                        void **p = (void**)e_start;     \
                                        gsize _bmap = (vt)->desc >> 16; \
@@ -1454,8 +1486,8 @@ mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
                }       \
        } while (0)
 
-//#include "sgen-major-copying.c"
-#include "sgen-marksweep.c"
+#include "sgen-major-copying.c"
+//#include "sgen-marksweep.c"
 
 static gboolean
 is_xdomain_ref_allowed (gpointer *ptr, char *obj, MonoDomain *domain)
@@ -1921,6 +1953,13 @@ mono_gc_clear_domain (MonoDomain * domain)
        scan_area_with_callback (nursery_section->data, nursery_section->end_data,
                        (IterateObjectCallbackFunc)clear_domain_process_minor_object_callback, domain);
 
+       /*Ephemerons and dislinks must be processed before LOS since they might end up pointing
+       to memory returned to the OS.*/
+       null_ephemerons_for_domain (domain);
+
+       for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
+               null_links_for_domain (domain, i);
+
        /* We need two passes over major and large objects because
           freeing such objects might give their memory back to the OS
           (in the case of large objects) or obliterate its vtable
@@ -1952,12 +1991,51 @@ mono_gc_clear_domain (MonoDomain * domain)
        major_iterate_objects (TRUE, FALSE, (IterateObjectCallbackFunc)clear_domain_free_major_non_pinned_object_callback, domain);
        major_iterate_objects (FALSE, TRUE, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain);
 
-       for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
-               null_links_for_domain (domain, i);
-
        UNLOCK_GC;
 }
 
+static void
+global_remset_cache_clear (void)
+{
+       memset (global_remset_cache, 0, sizeof (global_remset_cache));
+}
+
+/*
+ * Tries to check if a given remset location was already added to the global remset.
+ * It can
+ *
+ * A 2 entry, LRU cache of recently saw location remsets.
+ *
+ * It's hand-coded instead of done using loops to reduce the number of memory references on cache hit.
+ *
+ * Returns TRUE is the element was added..
+ */
+static gboolean
+global_remset_location_was_not_added (gpointer ptr)
+{
+
+       gpointer first = global_remset_cache [0], second;
+       if (first == ptr) {
+               HEAVY_STAT (++stat_global_remsets_discarded);
+               return FALSE;
+       }
+
+       second = global_remset_cache [1];
+
+       if (second == ptr) {
+               /*Move the second to the front*/
+               global_remset_cache [0] = second;
+               global_remset_cache [1] = first;
+
+               HEAVY_STAT (++stat_global_remsets_discarded);
+               return FALSE;
+       }
+
+       global_remset_cache [0] = second;
+       global_remset_cache [1] = ptr;
+       return TRUE;
+}
+
 /*
  * add_to_global_remset:
  *
@@ -1965,16 +2043,18 @@ mono_gc_clear_domain (MonoDomain * domain)
  * a minor collection. This can happen if the objects they point to are pinned.
  */
 static void
-add_to_global_remset (gpointer ptr, gboolean root)
+add_to_global_remset (gpointer ptr)
 {
        RememberedSet *rs;
 
+       g_assert (!ptr_in_nursery (ptr) && ptr_in_nursery (*(gpointer*)ptr));
+
+       if (!global_remset_location_was_not_added (ptr))
+               return;
+
        DEBUG (8, fprintf (gc_debug_file, "Adding global remset for %p\n", ptr));
        binary_protocol_global_remset (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
 
-       g_assert (!root);
-       g_assert (!ptr_in_nursery (ptr) && ptr_in_nursery (*(gpointer*)ptr));
-
        HEAVY_STAT (++stat_global_remsets_added);
 
        /* 
@@ -1982,23 +2062,13 @@ add_to_global_remset (gpointer ptr, gboolean root)
         * To avoid uncontrolled growth of the global remset, only add each pointer once.
         */
        if (global_remset->store_next + 3 < global_remset->end_set) {
-               if (root) {
-                       *(global_remset->store_next++) = (mword)ptr | REMSET_OTHER;
-                       *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION;
-               } else {
-                       *(global_remset->store_next++) = (mword)ptr;
-               }
+               *(global_remset->store_next++) = (mword)ptr;
                return;
        }
        rs = alloc_remset (global_remset->end_set - global_remset->data, NULL);
        rs->next = global_remset;
        global_remset = rs;
-       if (root) {
-               *(global_remset->store_next++) = (mword)ptr | REMSET_OTHER;
-               *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION;
-       } else {
-               *(global_remset->store_next++) = (mword)ptr;
-       }
+       *(global_remset->store_next++) = (mword)ptr;
 
        {
                int global_rs_size = 0;
@@ -2168,7 +2238,7 @@ copy_object (void **obj_slot)
                        __copy = *(ptr);        \
                        DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old));     \
                        if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
-                               add_to_global_remset ((ptr), FALSE);                                                    \
+                               add_to_global_remset ((ptr));           \
                }       \
        } while (0)
 
@@ -2235,7 +2305,7 @@ scan_vtype (char *start, mword desc, char* from_start, char* from_end)
                        __copy = *(ptr);        \
                        DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old));     \
                        if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
-                               add_to_global_remset ((ptr), FALSE);                                                    \
+                               add_to_global_remset ((ptr));           \
                }       \
        } while (0)
 
@@ -2390,41 +2460,54 @@ new_gap (int gap)
        return gap;
 }
 
-#if 0
-static int
-compare_addr (const void *a, const void *b)
-{
-       return *(const void **)a - *(const void **)b;
-}
-#endif
-
-/* sort the addresses in array in increasing order */
+/* Sort the addresses in array in increasing order.
+ * Done using a by-the book heap sort. Which has decent and stable performance, is pretty cache efficient.
+ */
 static void
 sort_addresses (void **array, int size)
 {
-       /*
-        * qsort is slower as predicted.
-        * qsort (array, size, sizeof (gpointer), compare_addr);
-        * return;
-        */
-       int gap = size;
-       int swapped, end;
-       while (TRUE) {
-               int i;
-               gap = new_gap (gap);
-               swapped = FALSE;
-               end = size - gap;
-               for (i = 0; i < end; i++) {
-                       int j = i + gap;
-                       if (array [i] > array [j]) {
-                               void* val = array [i];
-                               array [i] = array [j];
-                               array [j] = val;
-                               swapped = TRUE;
-                       }
+       int i;
+       void *tmp;
+
+       for (i = 1; i < size; ++i) {
+               int child = i;
+               while (child > 0) {
+                       int parent = (child - 1) / 2;
+
+                       if (array [parent] >= array [child])
+                               break;
+
+                       tmp = array [parent];
+                       array [parent] = array [child];
+                       array [child] = tmp;
+
+                       child = parent;
+               }
+       }
+
+       for (i = size - 1; i > 0; --i) {
+               int end, root;
+               tmp = array [i];
+               array [i] = array [0];
+               array [0] = tmp;
+
+               end = i - 1;
+               root = 0;
+
+               while (root * 2 + 1 <= end) {
+                       int child = root * 2 + 1;
+
+                       if (child < end && array [child] < array [child + 1])
+                               ++child;
+                       if (array [root] >= array [child])
+                               break;
+
+                       tmp = array [root];
+                       array [root] = array [child];
+                       array [child] = tmp;
+
+                       root = child;
                }
-               if (gap == 1 && !swapped)
-                       break;
        }
 }
 
@@ -2749,6 +2832,7 @@ add_nursery_frag (size_t frag_size, char* frag_start, char* frag_end)
                fragment_total += frag_size;
        } else {
                /* Clear unused fragments, pinning depends on this */
+               /*TODO place an int[] here instead of the memset if size justify it*/
                memset (frag_start, 0, frag_size);
        }
 }
@@ -2789,6 +2873,7 @@ finish_gray_stack (char *start_addr, char *end_addr, int generation)
        TV_DECLARE (atv);
        TV_DECLARE (btv);
        int fin_ready;
+       int ephemeron_rounds = 0;
        CopyOrMarkObjectFunc copy_func = current_collection_generation == GENERATION_NURSERY ? copy_object : major_copy_or_mark_object;
 
        /*
@@ -2815,6 +2900,20 @@ finish_gray_stack (char *start_addr, char *end_addr, int generation)
         * that are fin-ready. Speedup with a flag?
         */
        do {
+               /*
+                * Walk the ephemeron tables marking all values with reachable keys. This must be completely done
+                * before processing finalizable objects to avoid finalizing reachable values.
+                *
+                * It must be done inside the finalizaters loop since objects must not be removed from CWT tables
+                * while they are been finalized.
+                */
+               int done_with_ephemerons = 0;
+               do {
+                       done_with_ephemerons = mark_ephemerons_in_range (copy_func, start_addr, end_addr);
+                       drain_gray_stack ();
+                       ++ephemeron_rounds;
+               } while (!done_with_ephemerons);
+
                fin_ready = num_ready_finalizers;
                finalize_in_range (copy_func, start_addr, end_addr, generation);
                if (generation == GENERATION_OLD)
@@ -2824,8 +2923,15 @@ finish_gray_stack (char *start_addr, char *end_addr, int generation)
                DEBUG (6, fprintf (gc_debug_file, "Precise scan of gray area post fin\n"));
                drain_gray_stack ();
        } while (fin_ready != num_ready_finalizers);
+
+       /*
+        * Clear ephemeron pairs with unreachable keys.
+        * We pass the copy func so we can figure out if an array was promoted or not.
+        */
+       clear_unreachable_ephemerons (copy_func, start_addr, end_addr);
+
        TV_GETTIME (btv);
-       DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan for %s generation: %d usecs\n", generation_name (generation), TV_ELAPSED (atv, btv)));
+       DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan for %s generation: %d usecs %d ephemeron roundss\n", generation_name (generation), TV_ELAPSED (atv, btv), ephemeron_rounds));
 
        /*
         * handle disappearing links
@@ -3034,7 +3140,7 @@ dump_heap (const char *type, int num, const char *reason)
                                                     "fin-table", "finalize-entry", "dislink-table",
                                                     "dislink", "roots-table", "root-record", "statistics",
                                                     "remset", "gray-queue", "store-remset", "marksweep-tables",
-                                                    "marksweep-block-info" };
+                                                    "marksweep-block-info", "ephemeron-link" };
 
        ObjectList *list;
        LOSObject *bigobj;
@@ -3044,8 +3150,8 @@ dump_heap (const char *type, int num, const char *reason)
        if (reason)
                fprintf (heap_dump_file, " reason=\"%s\"", reason);
        fprintf (heap_dump_file, ">\n");
-       fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%ld\"/>\n", pinned_chunk_bytes_alloced);
-       fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%ld\"/>\n", large_internal_bytes_alloced);
+       fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%lld\"/>\n", pinned_chunk_bytes_alloced);
+       fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%lld\"/>\n", large_internal_bytes_alloced);
        fprintf (heap_dump_file, "<other-mem-usage type=\"mempools\" size=\"%ld\"/>\n", mono_mempool_get_bytes_allocated ());
        for (i = 0; i < INTERNAL_MEM_MAX; ++i)
                fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n", internal_mem_names [i], small_internal_mem_bytes [i]);
@@ -3127,12 +3233,18 @@ init_stats (void)
        mono_counters_register ("# nursery copy_object() failed forwarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_forwarded);
        mono_counters_register ("# nursery copy_object() failed pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_pinned);
 
+       mono_counters_register ("# wasted fragments used", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_used);
+       mono_counters_register ("bytes in wasted fragments", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_bytes);
+
        mono_counters_register ("Store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets);
        mono_counters_register ("Unique store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets_unique);
        mono_counters_register ("Saved remsets 1", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_1);
        mono_counters_register ("Saved remsets 2", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_2);
        mono_counters_register ("Global remsets added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_added);
+       mono_counters_register ("Global remsets re-added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_readded);
        mono_counters_register ("Global remsets processed", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_processed);
+       mono_counters_register ("Global remsets discarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_discarded);
+
 #endif
 
        inited = TRUE;
@@ -3189,6 +3301,9 @@ collect_nursery (size_t requested_size)
 
        num_minor_gcs++;
        mono_stats.minor_gc_count ++;
+
+       global_remset_cache_clear ();
+
        /* pin from pinned handles */
        init_pinning ();
        pin_from_roots (nursery_start, nursery_next);
@@ -3270,6 +3385,219 @@ collect_nursery (size_t requested_size)
        return major_need_major_collection ();
 }
 
+static void
+major_do_collection (const char *reason)
+{
+       LOSObject *bigobj, *prevbo;
+       TV_DECLARE (all_atv);
+       TV_DECLARE (all_btv);
+       TV_DECLARE (atv);
+       TV_DECLARE (btv);
+       /* FIXME: only use these values for the precise scan
+        * note that to_space pointers should be excluded anyway...
+        */
+       char *heap_start = NULL;
+       char *heap_end = (char*)-1;
+       int old_num_major_sections = num_major_sections;
+       int num_major_sections_saved, save_target, allowance_target;
+
+       //count_ref_nonref_objs ();
+       //consistency_check ();
+
+       init_stats ();
+       binary_protocol_collection (GENERATION_OLD);
+       check_scan_starts ();
+       gray_object_queue_init ();
+
+       degraded_mode = 0;
+       DEBUG (1, fprintf (gc_debug_file, "Start major collection %d\n", num_major_gcs));
+       num_major_gcs++;
+       mono_stats.major_gc_count ++;
+
+       /* world must be stopped already */
+       TV_GETTIME (all_atv);
+       TV_GETTIME (atv);
+
+       /* Pinning depends on this */
+       clear_nursery_fragments (nursery_next);
+
+       TV_GETTIME (btv);
+       time_major_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
+
+       if (xdomain_checks)
+               check_for_xdomain_refs ();
+
+       nursery_section->next_data = nursery_real_end;
+       /* we should also coalesce scanning from sections close to each other
+        * and deal with pointers outside of the sections later.
+        */
+       /* The remsets are not useful for a major collection */
+       clear_remsets ();
+       global_remset_cache_clear ();
+
+       TV_GETTIME (atv);
+       init_pinning ();
+       DEBUG (6, fprintf (gc_debug_file, "Collecting pinned addresses\n"));
+       pin_from_roots ((void*)lowest_heap_address, (void*)highest_heap_address);
+       optimize_pin_queue (0);
+
+       /*
+        * pin_queue now contains all candidate pointers, sorted and
+        * uniqued.  We must do two passes now to figure out which
+        * objects are pinned.
+        *
+        * The first is to find within the pin_queue the area for each
+        * section.  This requires that the pin_queue be sorted.  We
+        * also process the LOS objects and pinned chunks here.
+        *
+        * The second, destructive, pass is to reduce the section
+        * areas to pointers to the actually pinned objects.
+        */
+       DEBUG (6, fprintf (gc_debug_file, "Pinning from sections\n"));
+       /* first pass for the sections */
+       find_section_pin_queue_start_end (nursery_section);
+       major_find_pin_queue_start_ends ();
+       /* identify possible pointers to the insize of large objects */
+       DEBUG (6, fprintf (gc_debug_file, "Pinning from large objects\n"));
+       for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
+               int start, end;
+               find_optimized_pin_queue_area (bigobj->data, (char*)bigobj->data + bigobj->size, &start, &end);
+               if (start != end) {
+                       pin_object (bigobj->data);
+                       /* FIXME: only enqueue if object has references */
+                       GRAY_OBJECT_ENQUEUE (bigobj->data);
+                       if (heap_dump_file)
+                               pin_stats_register_object ((char*) bigobj->data, safe_object_get_size ((MonoObject*) bigobj->data));
+                       DEBUG (6, fprintf (gc_debug_file, "Marked large object %p (%s) size: %zd from roots\n", bigobj->data, safe_name (bigobj->data), bigobj->size));
+               }
+       }
+       /* second pass for the sections */
+       pin_objects_in_section (nursery_section);
+       major_pin_objects ();
+
+       TV_GETTIME (btv);
+       time_major_pinning += TV_ELAPSED_MS (atv, btv);
+       DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (atv, btv)));
+       DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
+
+       major_init_to_space ();
+
+       drain_gray_stack ();
+
+       TV_GETTIME (atv);
+       time_major_scan_pinned += TV_ELAPSED_MS (btv, atv);
+
+       /* registered roots, this includes static fields */
+       scan_from_registered_roots (major_copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_NORMAL);
+       scan_from_registered_roots (major_copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_WBARRIER);
+       TV_GETTIME (btv);
+       time_major_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
+
+       /* Threads */
+       /* FIXME: This is the wrong place for this, because it does
+          pinning */
+       scan_thread_data (heap_start, heap_end, TRUE);
+       TV_GETTIME (atv);
+       time_major_scan_thread_data += TV_ELAPSED_MS (btv, atv);
+
+       TV_GETTIME (btv);
+       time_major_scan_alloc_pinned += TV_ELAPSED_MS (atv, btv);
+
+       /* scan the list of objects ready for finalization */
+       scan_finalizer_entries (major_copy_or_mark_object, fin_ready_list);
+       scan_finalizer_entries (major_copy_or_mark_object, critical_fin_list);
+       TV_GETTIME (atv);
+       time_major_scan_finalized += TV_ELAPSED_MS (btv, atv);
+       DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (btv, atv)));
+
+       TV_GETTIME (btv);
+       time_major_scan_big_objects += TV_ELAPSED_MS (atv, btv);
+
+       /* all the objects in the heap */
+       finish_gray_stack (heap_start, heap_end, GENERATION_OLD);
+       TV_GETTIME (atv);
+       time_major_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
+
+       /* sweep the big objects list */
+       prevbo = NULL;
+       for (bigobj = los_object_list; bigobj;) {
+               if (object_is_pinned (bigobj->data)) {
+                       unpin_object (bigobj->data);
+               } else {
+                       LOSObject *to_free;
+                       /* not referenced anywhere, so we can free it */
+                       if (prevbo)
+                               prevbo->next = bigobj->next;
+                       else
+                               los_object_list = bigobj->next;
+                       to_free = bigobj;
+                       bigobj = bigobj->next;
+                       free_large_object (to_free);
+                       continue;
+               }
+               prevbo = bigobj;
+               bigobj = bigobj->next;
+       }
+
+       major_sweep ();
+
+       TV_GETTIME (btv);
+       time_major_sweep += TV_ELAPSED_MS (atv, btv);
+
+       /* walk the pin_queue, build up the fragment list of free memory, unmark
+        * pinned objects as we go, memzero() the empty fragments so they are ready for the
+        * next allocations.
+        */
+       build_nursery_fragments (nursery_section->pin_queue_start, nursery_section->pin_queue_end);
+
+       TV_GETTIME (atv);
+       time_major_fragment_creation += TV_ELAPSED_MS (btv, atv);
+
+       TV_GETTIME (all_btv);
+       mono_stats.major_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
+
+       if (heap_dump_file)
+               dump_heap ("major", num_major_gcs - 1, reason);
+
+       /* prepare the pin queue for the next collection */
+       next_pin_slot = 0;
+       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 ();
+       }
+       pin_stats_reset ();
+
+       g_assert (gray_object_queue_is_empty ());
+
+       num_major_sections_saved = MAX (old_num_major_sections - num_major_sections, 1);
+
+       save_target = num_major_sections / 2;
+       /*
+        * We aim to allow the allocation of as many sections as is
+        * necessary to reclaim save_target sections in the next
+        * collection.  We assume the collection pattern won't change.
+        * In the last cycle, we had num_major_sections_saved for
+        * minor_collection_sections_alloced.  Assuming things won't
+        * change, this must be the same ratio as save_target for
+        * allowance_target, i.e.
+        *
+        *    num_major_sections_saved            save_target
+        * --------------------------------- == ----------------
+        * minor_collection_sections_alloced    allowance_target
+        *
+        * hence:
+        */
+       allowance_target = save_target * minor_collection_sections_alloced / num_major_sections_saved;
+
+       minor_collection_section_allowance = MAX (MIN (allowance_target, num_major_sections), MIN_MINOR_COLLECTION_SECTION_ALLOWANCE);
+
+       minor_collection_sections_alloced = 0;
+
+       check_scan_starts ();
+
+       //consistency_check ();
+}
+
 static void
 major_collection (const char *reason)
 {
@@ -3676,6 +4004,22 @@ alloc_large_inner (MonoVTable *vtable, size_t size)
        return obj->data;
 }
 
+static void
+setup_fragment (Fragment *frag, Fragment *prev, size_t size)
+{
+       /* remove from the list */
+       if (prev)
+               prev->next = frag->next;
+       else
+               nursery_fragments = frag->next;
+       nursery_next = frag->fragment_start;
+       nursery_frag_real_end = frag->fragment_end;
+
+       DEBUG (4, fprintf (gc_debug_file, "Using nursery fragment %p-%p, size: %zd (req: %zd)\n", nursery_next, nursery_frag_real_end, nursery_frag_real_end - nursery_next, size));
+       frag->next = fragment_freelist;
+       fragment_freelist = frag;
+}
+
 /* check if we have a suitable fragment in nursery_fragments to be able to allocate
  * an object of size @size
  * Return FALSE if not found (which means we need a collection)
@@ -3693,17 +4037,7 @@ search_fragment_for_size (size_t size)
        prev = NULL;
        for (frag = nursery_fragments; frag; frag = frag->next) {
                if (size <= (frag->fragment_end - frag->fragment_start)) {
-                       /* remove from the list */
-                       if (prev)
-                               prev->next = frag->next;
-                       else
-                               nursery_fragments = frag->next;
-                       nursery_next = frag->fragment_start;
-                       nursery_frag_real_end = frag->fragment_end;
-
-                       DEBUG (4, fprintf (gc_debug_file, "Using nursery fragment %p-%p, size: %zd (req: %zd)\n", nursery_next, nursery_frag_real_end, nursery_frag_real_end - nursery_next, size));
-                       frag->next = fragment_freelist;
-                       fragment_freelist = frag;
+                       setup_fragment (frag, prev, size);
                        return TRUE;
                }
                prev = frag;
@@ -3711,6 +4045,53 @@ search_fragment_for_size (size_t size)
        return FALSE;
 }
 
+/*
+ * Same as search_fragment_for_size but if search for @desired_size fails, try to satisfy @minimum_size.
+ * This improves nursery usage.
+ */
+static int
+search_fragment_for_size_range (size_t desired_size, size_t minimum_size)
+{
+       Fragment *frag, *prev, *min_prev;
+       DEBUG (4, fprintf (gc_debug_file, "Searching nursery fragment %p, desired size: %zd minimum size %zd\n", nursery_frag_real_end, desired_size, minimum_size));
+
+       if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
+               /* Clear the remaining space, pinning depends on this */
+               memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
+
+       min_prev = GINT_TO_POINTER (-1);
+       prev = NULL;
+
+       for (frag = nursery_fragments; frag; frag = frag->next) {
+               int frag_size = frag->fragment_end - frag->fragment_start;
+               if (desired_size <= frag_size) {
+                       setup_fragment (frag, prev, desired_size);
+                       return desired_size;
+               }
+               if (minimum_size <= frag_size)
+                       min_prev = prev;
+
+               prev = frag;
+       }
+
+       if (min_prev != GINT_TO_POINTER (-1)) {
+               int frag_size;
+               if (min_prev)
+                       frag = min_prev->next;
+               else
+                       frag = nursery_fragments;
+
+               frag_size = frag->fragment_end - frag->fragment_start;
+               HEAVY_STAT (++stat_wasted_fragments_used);
+               HEAVY_STAT (stat_wasted_fragments_bytes += frag_size);
+
+               setup_fragment (frag, min_prev, minimum_size);
+               return frag_size;
+       }
+
+       return 0;
+}
+
 /*
  * Provide a variant that takes just the vtable for small fixed-size objects.
  * The aligned size is already computed and stored in vt->gc_descr.
@@ -3725,7 +4106,6 @@ mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
        /* FIXME: handle OOM */
        void **p;
        char *new_next;
-       gboolean res;
        TLAB_ACCESS_INIT;
 
        HEAVY_STAT (++stat_objects_alloced);
@@ -3819,6 +4199,7 @@ mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
                                return p;
                        }
 
+                       /*FIXME This codepath is current deadcode since tlab_size > MAX_SMALL_OBJ_SIZE*/
                        if (size > tlab_size) {
                                /* Allocate directly from the nursery */
                                if (nursery_next + size >= nursery_frag_real_end) {
@@ -3841,29 +4222,36 @@ mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
                                if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
                                        memset (p, 0, size);
                        } else {
+                               int alloc_size = tlab_size;
+                               int available_in_nursery = nursery_frag_real_end - nursery_next;
                                if (TLAB_START)
                                        DEBUG (3, fprintf (gc_debug_file, "Retire TLAB: %p-%p [%ld]\n", TLAB_START, TLAB_REAL_END, (long)(TLAB_REAL_END - TLAB_NEXT - size)));
 
-                               if (nursery_next + tlab_size >= nursery_frag_real_end) {
-                                       res = search_fragment_for_size (tlab_size);
-                                       if (!res) {
-                                               minor_collect_or_expand_inner (tlab_size);
-                                               if (degraded_mode) {
-                                                       p = alloc_degraded (vtable, size);
-                                                       return p;
+                               if (alloc_size >= available_in_nursery) {
+                                       if (available_in_nursery > MAX_NURSERY_TLAB_WASTE && available_in_nursery > size) {
+                                               alloc_size = available_in_nursery;
+                                       } else {
+                                               alloc_size = search_fragment_for_size_range (tlab_size, size);
+                                               if (!alloc_size) {
+                                                       alloc_size = tlab_size;
+                                                       minor_collect_or_expand_inner (tlab_size);
+                                                       if (degraded_mode) {
+                                                               p = alloc_degraded (vtable, size);
+                                                               return p;
+                                                       }
                                                }
                                        }
                                }
 
                                /* Allocate a new TLAB from the current nursery fragment */
                                TLAB_START = nursery_next;
-                               nursery_next += tlab_size;
+                               nursery_next += alloc_size;
                                TLAB_NEXT = TLAB_START;
-                               TLAB_REAL_END = TLAB_START + tlab_size;
-                               TLAB_TEMP_END = TLAB_START + MIN (SCAN_START_SIZE, tlab_size);
+                               TLAB_REAL_END = TLAB_START + alloc_size;
+                               TLAB_TEMP_END = TLAB_START + MIN (SCAN_START_SIZE, alloc_size);
 
                                if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
-                                       memset (TLAB_START, 0, tlab_size);
+                                       memset (TLAB_START, 0, alloc_size);
 
                                /* Allocate from the TLAB */
                                p = (void*)TLAB_NEXT;
@@ -4199,6 +4587,174 @@ finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int g
        }
 }
 
+static int
+object_is_reachable (char *object, char *start, char *end)
+{
+       /*This happens for non nursery objects during minor collections. We just treat all objects as alive.*/
+       if (object < start || object >= end)
+               return TRUE;
+       return !object_is_fin_ready (object) || major_is_object_live (object);
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+null_ephemerons_for_domain (MonoDomain *domain)
+{
+       EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
+
+       while (current) {
+               MonoObject *object = (MonoObject*)current->array;
+
+               if (object && !object->vtable) {
+                       EphemeronLinkNode *tmp = current;
+
+                       if (prev)
+                               prev->next = current->next;
+                       else
+                               ephemeron_list = current->next;
+
+                       current = current->next;
+                       free_internal_mem (tmp, INTERNAL_MEM_EPHEMERON_LINK);
+               } else {
+                       prev = current;
+                       current = current->next;
+               }
+       }
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end)
+{
+       int was_in_nursery, was_promoted;
+       EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
+       MonoArray *array;
+       Ephemeron *cur, *array_end;
+       char *tombstone;
+
+       while (current) {
+               char *object = current->array;
+
+               if (!object_is_reachable (object, start, end)) {
+                       EphemeronLinkNode *tmp = current;
+
+                       DEBUG (5, fprintf (gc_debug_file, "Dead Ephemeron array at %p\n", object));
+
+                       if (prev)
+                               prev->next = current->next;
+                       else
+                               ephemeron_list = current->next;
+
+                       current = current->next;
+                       free_internal_mem (tmp, INTERNAL_MEM_EPHEMERON_LINK);
+
+                       continue;
+               }
+
+               was_in_nursery = ptr_in_nursery (object);
+               copy_func ((void**)&object);
+               current->array = object;
+
+               /*The array was promoted, add global remsets for key/values left behind in nursery.*/
+               was_promoted = was_in_nursery && !ptr_in_nursery (object);
+
+               DEBUG (5, fprintf (gc_debug_file, "Clearing unreachable entries for ephemeron array at %p\n", object));
+
+               array = (MonoArray*)object;
+               cur = mono_array_addr (array, Ephemeron, 0);
+               array_end = cur + mono_array_length_fast (array);
+               tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
+
+               for (; cur < array_end; ++cur) {
+                       char *key = (char*)cur->key;
+
+                       if (!key || key == tombstone)
+                               continue;
+
+                       DEBUG (5, fprintf (gc_debug_file, "[%d] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
+                               key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
+                               cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
+
+                       if (!object_is_reachable (key, start, end)) {
+                               cur->key = tombstone;
+                               cur->value = NULL;
+                               continue;
+                       }
+
+                       if (was_promoted) {
+                               if (ptr_in_nursery (key)) {/*key was not promoted*/
+                                       DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to key %p\n", key));
+                                       add_to_global_remset (&cur->key);
+                               }
+                               if (ptr_in_nursery (cur->value)) {/*value was not promoted*/
+                                       DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to value %p\n", cur->value));
+                                       add_to_global_remset (&cur->value);
+                               }
+                       }
+               }
+               prev = current;
+               current = current->next;
+       }
+}
+
+/* LOCKING: requires that the GC lock is held */
+static int
+mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end)
+{
+       int nothing_marked = 1;
+       EphemeronLinkNode *current = ephemeron_list;
+       MonoArray *array;
+       Ephemeron *cur, *array_end;
+       char *tombstone;
+
+       for (current = ephemeron_list; current; current = current->next) {
+               char *object = current->array;
+               DEBUG (5, fprintf (gc_debug_file, "Ephemeron array at %p\n", object));
+
+               /*We ignore arrays in old gen during minor collections since all objects are promoted by the remset machinery.*/
+               if (object < start || object >= end)
+                       continue;
+
+               /*It has to be alive*/
+               if (!object_is_reachable (object, start, end)) {
+                       DEBUG (5, fprintf (gc_debug_file, "\tnot reachable\n"));
+                       continue;
+               }
+
+               copy_func ((void**)&object);
+
+               array = (MonoArray*)object;
+               cur = mono_array_addr (array, Ephemeron, 0);
+               array_end = cur + mono_array_length_fast (array);
+               tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
+
+               for (; cur < array_end; ++cur) {
+                       char *key = cur->key;
+
+                       if (!key || key == tombstone)
+                               continue;
+
+                       DEBUG (5, fprintf (gc_debug_file, "[%d] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
+                               key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
+                               cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
+
+                       if (object_is_reachable (key, start, end)) {
+                               char *value = cur->value;
+
+                               copy_func ((void**)&cur->key);
+                               if (value) {
+                                       if (!object_is_reachable (value, start, end))
+                                               nothing_marked = 0;
+                                       copy_func ((void**)&cur->value);
+                               }
+                       }
+               }
+       }
+
+       DEBUG (5, fprintf (gc_debug_file, "Ephemeron run finished. Is it done %d\n", nothing_marked));
+       return nothing_marked;
+}
+
 /* LOCKING: requires that the GC lock is held */
 static void
 null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation)
@@ -4294,11 +4850,7 @@ null_links_for_domain (MonoDomain *domain, int generation)
                prev = NULL;
                for (entry = disappearing_link_hash [i]; entry; ) {
                        char *object = DISLINK_OBJECT (entry);
-                       /* FIXME: actually there should be no object
-                          left in the domain with a non-null vtable
-                          (provided we remove the Thread special
-                          case) */
-                       if (object && (!((MonoObject*)object)->vtable || mono_object_domain (object) == domain)) {
+                       if (object && !((MonoObject*)object)->vtable) {
                                DisappearingLink *next = entry->next;
 
                                if (prev)
@@ -5216,7 +5768,7 @@ handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global
                                 * becomes part of the global remset, which can grow very large.
                                 */
                                DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
-                               add_to_global_remset (ptr, FALSE);
+                               add_to_global_remset (ptr);
                        }
                } else {
                        DEBUG (9, fprintf (gc_debug_file, "Skipping remset at %p holding %p\n", ptr, *ptr));
@@ -5231,7 +5783,7 @@ handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global
                        copy_object (ptr);
                        DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p (count: %d)\n", ptr, *ptr, (int)count));
                        if (!global && *ptr >= start_nursery && *ptr < end_nursery)
-                               add_to_global_remset (ptr, FALSE);
+                               add_to_global_remset (ptr);
                        ++ptr;
                }
                return p + 2;
@@ -5241,35 +5793,15 @@ handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global
                        return p + 1;
                scan_object ((char*)ptr);
                return p + 1;
-       case REMSET_OTHER: {
+       case REMSET_VTYPE: {
                ptr = (void**)(*p & ~REMSET_TYPE_MASK);
-
-               switch (p [1]) {
-               case REMSET_VTYPE:
-                       if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
-                               return p + 4;
-                       desc = p [2];
-                       count = p [3];
-                       while (count-- > 0)
-                               ptr = (void**) scan_vtype ((char*)ptr, desc, start_nursery, end_nursery);
-                       return p + 4;
-               case REMSET_ROOT_LOCATION:
-                       /* Same as REMSET_LOCATION, but the address is not required to be in the heap */
-                       copy_object (ptr);
-                       DEBUG (9, fprintf (gc_debug_file, "Overwrote root location remset at %p with %p\n", ptr, *ptr));
-                       if (!global && *ptr >= start_nursery && *ptr < end_nursery) {
-                               /*
-                                * If the object is pinned, each reference to it from nonpinned objects
-                                * becomes part of the global remset, which can grow very large.
-                                */
-                               DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
-                               add_to_global_remset (ptr, TRUE);
-                       }
-                       return p + 2;
-               default:
-                       g_assert_not_reached ();
-               }
-               break;
+               if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
+                       return p + 3;
+               desc = p [1];
+               count = p [2];
+               while (count-- > 0)
+                       ptr = (void**) scan_vtype ((char*)ptr, desc, start_nursery, end_nursery);
+               return p + 3;
        }
        default:
                g_assert_not_reached ();
@@ -5307,17 +5839,8 @@ collect_store_remsets (RememberedSet *remset, mword *bumper)
                case REMSET_OBJECT:
                        p += 1;
                        break;
-               case REMSET_OTHER:
-                       switch (p [1]) {
-                       case REMSET_VTYPE:
-                               p += 4;
-                               break;
-                       case REMSET_ROOT_LOCATION:
-                               p += 2;
-                               break;
-                       default:
-                               g_assert_not_reached ();
-                       }
+               case REMSET_VTYPE:
+                       p += 3;
                        break;
                default:
                        g_assert_not_reached ();
@@ -5404,7 +5927,13 @@ scan_from_remsets (void *start_nursery, void *end_nursery)
                DEBUG (4, fprintf (gc_debug_file, "Scanning global remset range: %p-%p, size: %zd\n", remset->data, remset->store_next, remset->store_next - remset->data));
                store_pos = remset->data;
                for (p = remset->data; p < remset->store_next; p = next_p) {
-                       mword ptr;
+                       void **ptr = p [0];
+
+                       /*Ignore previously processed remset.*/
+                       if (!global_remset_location_was_not_added (ptr)) {
+                               next_p = p + 1;
+                               continue;
+                       }
 
                        next_p = handle_remset (p, start_nursery, end_nursery, TRUE);
 
@@ -5412,18 +5941,12 @@ scan_from_remsets (void *start_nursery, void *end_nursery)
                         * Clear global remsets of locations which no longer point to the 
                         * nursery. Otherwise, they could grow indefinitely between major 
                         * collections.
+                        *
+                        * Since all global remsets are location remsets, we don't need to unmask the pointer.
                         */
-                       ptr = (p [0] & ~REMSET_TYPE_MASK);
-                       if ((p [0] & REMSET_TYPE_MASK) == REMSET_LOCATION) {
-                               if (ptr_in_nursery (*(void**)ptr))
-                                       *store_pos ++ = p [0];
-                       } else {
-                               g_assert ((p [0] & REMSET_TYPE_MASK) == REMSET_OTHER);
-                               g_assert (p [1] == REMSET_ROOT_LOCATION);
-                               if (ptr_in_nursery (*(void**)ptr)) {
-                                       *store_pos ++ = p [0];
-                                       *store_pos ++ = p [1];
-                               }
+                       if (ptr_in_nursery (*ptr)) {
+                               *store_pos ++ = p [0];
+                               HEAVY_STAT (++stat_global_remsets_readded);
                        }
                }
 
@@ -6063,35 +6586,6 @@ mono_gc_wbarrier_generic_store (gpointer ptr, MonoObject* value)
                mono_gc_wbarrier_generic_nostore (ptr);
 }
 
-void
-mono_gc_wbarrier_set_root (gpointer ptr, MonoObject *value)
-{
-       RememberedSet *rs;
-       TLAB_ACCESS_INIT;
-       HEAVY_STAT (++stat_wbarrier_set_root);
-       if (ptr_in_nursery (ptr))
-               return;
-       DEBUG (8, fprintf (gc_debug_file, "Adding root remset at %p (%s)\n", ptr, value ? safe_name (value) : "null"));
-
-       rs = REMEMBERED_SET;
-       if (rs->store_next + 2 < rs->end_set) {
-               *(rs->store_next++) = (mword)ptr | REMSET_OTHER;
-               *(rs->store_next++) = (mword)REMSET_ROOT_LOCATION;
-               *(void**)ptr = value;
-               return;
-       }
-       rs = alloc_remset (rs->end_set - rs->data, (void*)1);
-       rs->next = REMEMBERED_SET;
-       REMEMBERED_SET = rs;
-#ifdef HAVE_KW_THREAD
-       thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
-#endif
-       *(rs->store_next++) = (mword)ptr | REMSET_OTHER;
-       *(rs->store_next++) = (mword)REMSET_ROOT_LOCATION;
-
-       *(void**)ptr = value;
-}
-
 void
 mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *klass)
 {
@@ -6110,8 +6604,7 @@ mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *
        DEBUG (8, fprintf (gc_debug_file, "Adding value remset at %p, count %d, descr %p for class %s (%p)\n", dest, count, klass->gc_descr, klass->name, klass));
 
        if (rs->store_next + 3 < rs->end_set) {
-               *(rs->store_next++) = (mword)dest | REMSET_OTHER;
-               *(rs->store_next++) = (mword)REMSET_VTYPE;
+               *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
                *(rs->store_next++) = (mword)klass->gc_descr;
                *(rs->store_next++) = (mword)count;
                UNLOCK_GC;
@@ -6123,8 +6616,7 @@ mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *
 #ifdef HAVE_KW_THREAD
        thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
 #endif
-       *(rs->store_next++) = (mword)dest | REMSET_OTHER;
-       *(rs->store_next++) = (mword)REMSET_VTYPE;
+       *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
        *(rs->store_next++) = (mword)klass->gc_descr;
        *(rs->store_next++) = (mword)count;
        UNLOCK_GC;
@@ -6260,39 +6752,30 @@ find_in_remset_loc (mword *p, char *addr, gboolean *found)
                if ((void**)addr >= ptr && (void**)addr < ptr + count)
                        *found = TRUE;
                return p + 1;
-       case REMSET_OTHER: {
-               switch (p [1]) {
-               case REMSET_VTYPE:
-                       ptr = (void**)(*p & ~REMSET_TYPE_MASK);
-                       desc = p [2];
-                       count = p [3];
-
-                       switch (desc & 0x7) {
-                       case DESC_TYPE_RUN_LENGTH:
-                               OBJ_RUN_LEN_SIZE (skip_size, desc, ptr);
-                               break;
-                       case DESC_TYPE_SMALL_BITMAP:
-                               OBJ_BITMAP_SIZE (skip_size, desc, start);
-                               break;
-                       default:
-                               // FIXME:
-                               g_assert_not_reached ();
-                       }
-
-                       /* The descriptor includes the size of MonoObject */
-                       skip_size -= sizeof (MonoObject);
-                       skip_size *= count;
-                       if ((void**)addr >= ptr && (void**)addr < ptr + (skip_size / sizeof (gpointer)))
-                               *found = TRUE;
+       case REMSET_VTYPE:
+               ptr = (void**)(*p & ~REMSET_TYPE_MASK);
+               desc = p [1];
+               count = p [2];
 
-                       return p + 4;
-               case REMSET_ROOT_LOCATION:
-                       return p + 2;
+               switch (desc & 0x7) {
+               case DESC_TYPE_RUN_LENGTH:
+                       OBJ_RUN_LEN_SIZE (skip_size, desc, ptr);
+                       break;
+               case DESC_TYPE_SMALL_BITMAP:
+                       OBJ_BITMAP_SIZE (skip_size, desc, start);
+                       break;
                default:
+                       // FIXME:
                        g_assert_not_reached ();
                }
-               break;
-       }
+
+               /* The descriptor includes the size of MonoObject */
+               skip_size -= sizeof (MonoObject);
+               skip_size *= count;
+               if ((void**)addr >= ptr && (void**)addr < ptr + (skip_size / sizeof (gpointer)))
+                       *found = TRUE;
+
+               return p + 3;
        default:
                g_assert_not_reached ();
        }
@@ -6564,6 +7047,28 @@ mono_gc_weak_link_get (void **link_addr)
        return (MonoObject*) REVEAL_POINTER (*link_addr);
 }
 
+gboolean
+mono_gc_ephemeron_array_add (MonoObject *obj)
+{
+       EphemeronLinkNode *node;
+
+       LOCK_GC;
+
+       node = get_internal_mem (sizeof (EphemeronLinkNode), INTERNAL_MEM_EPHEMERON_LINK);
+       if (!node) {
+               UNLOCK_GC;
+               return FALSE;
+       }
+       node->array = (char*)obj;
+       node->next = ephemeron_list;
+       ephemeron_list = node;
+
+       DEBUG (5, fprintf (gc_debug_file, "Registered ephemeron array %p\n", obj));
+
+       UNLOCK_GC;
+       return TRUE;
+}
+
 void*
 mono_gc_make_descr_from_bitmap (gsize *bitmap, int numbits)
 {