Merge branch 'alexischr/nursery-canaries-managed-alloc'
[mono.git] / mono / metadata / sgen-mono.c
index a822dd057bb2874e60e3d006a1ddd0cb54228eec..6930b8e3f5e91caefd0ea61d76127238595da906 100644 (file)
@@ -3,18 +3,7 @@
  *
  * Copyright (C) 2014 Xamarin Inc
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License 2.0 as published by the Free Software Foundation;
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License 2.0 along with this library; if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
  */
 
 #include "config.h"
@@ -27,6 +16,7 @@
 #include "sgen/sgen-client.h"
 #include "sgen/sgen-cardtable.h"
 #include "sgen/sgen-pinning.h"
+#include "sgen/sgen-thread-pool.h"
 #include "metadata/marshal.h"
 #include "metadata/method-builder.h"
 #include "metadata/abi-details.h"
@@ -37,6 +27,9 @@
 #include "metadata/handle.h"
 #include "utils/mono-memory-model.h"
 #include "utils/mono-logger-internals.h"
+#include "utils/mono-threads-coop.h"
+#include "sgen/sgen-thread-pool.h"
+#include "utils/mono-threads.h"
 
 #ifdef HEAVY_STATISTICS
 static guint64 stat_wbarrier_set_arrayref = 0;
@@ -149,7 +142,8 @@ mono_gc_wbarrier_object_copy (MonoObject* obj, MonoObject *src)
 
        HEAVY_STAT (++stat_wbarrier_object_copy);
 
-       if (sgen_ptr_in_nursery (obj) || ptr_on_stack (obj) || !SGEN_OBJECT_HAS_REFERENCES (src)) {
+       SGEN_ASSERT (6, !ptr_on_stack (obj), "Why is this called for a non-reference type?");
+       if (sgen_ptr_in_nursery (obj) || !SGEN_OBJECT_HAS_REFERENCES (src)) {
                size = mono_object_class (obj)->instance_size;
                mono_gc_memmove_aligned ((char*)obj + sizeof (MonoObject), (char*)src + sizeof (MonoObject),
                                size - sizeof (MonoObject));
@@ -191,19 +185,57 @@ mono_gc_wbarrier_value_copy_bitmap (gpointer _dest, gpointer _src, int size, uns
        sgen_wbarrier_value_copy_bitmap (_dest, _src, size, bitmap);
 }
 
+int
+mono_gc_get_suspend_signal (void)
+{
+       return mono_threads_suspend_get_suspend_signal ();
+}
+
+int
+mono_gc_get_restart_signal (void)
+{
+       return mono_threads_suspend_get_restart_signal ();
+}
+
 static MonoMethod *write_barrier_conc_method;
 static MonoMethod *write_barrier_noconc_method;
 
 gboolean
 sgen_is_critical_method (MonoMethod *method)
 {
-       return (method == write_barrier_conc_method || method == write_barrier_noconc_method || sgen_is_managed_allocator (method));
+       return sgen_is_managed_allocator (method);
 }
 
 gboolean
 sgen_has_critical_method (void)
 {
-       return write_barrier_conc_method || write_barrier_noconc_method || sgen_has_managed_allocator ();
+       return sgen_has_managed_allocator ();
+}
+
+static gboolean
+ip_in_critical_region (MonoDomain *domain, gpointer ip)
+{
+       MonoJitInfo *ji;
+       MonoMethod *method;
+
+       /*
+        * We pass false for 'try_aot' so this becomes async safe.
+        * It won't find aot methods whose jit info is not yet loaded,
+        * so we preload their jit info in the JIT.
+        */
+       ji = mono_jit_info_table_find_internal (domain, ip, FALSE, FALSE);
+       if (!ji)
+               return FALSE;
+
+       method = mono_jit_info_get_method (ji);
+
+       return mono_runtime_is_critical_method (method) || sgen_is_critical_method (method);
+}
+
+gboolean
+mono_gc_is_critical_method (MonoMethod *method)
+{
+       return sgen_is_critical_method (method);
 }
 
 #ifndef DISABLE_JIT
@@ -499,7 +531,7 @@ mono_gc_invoke_finalizers (void)
        return sgen_gc_invoke_finalizers ();
 }
 
-gboolean
+MonoBoolean
 mono_gc_pending_finalizers (void)
 {
        return sgen_have_pending_finalizers ();
@@ -534,17 +566,19 @@ object_in_domain_predicate (MonoObject *obj, void *user_data)
  * @out_array: output array
  * @out_size: size of output array
  *
- * Store inside @out_array up to @out_size objects that belong to the unloading
- * appdomain @domain. Returns the number of stored items. Can be called repeteadly
- * until it returns 0.
- * The items are removed from the finalizer data structure, so the caller is supposed
- * to finalize them.
- * @out_array should be on the stack to allow the GC to know the objects are still alive.
+ * Enqueue for finalization all objects that belong to the unloading appdomain @domain
+ * @suspend is used for early termination of the enqueuing process.
  */
-int
-mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
+void
+mono_gc_finalize_domain (MonoDomain *domain)
+{
+       sgen_finalize_if (object_in_domain_predicate, domain);
+}
+
+void
+mono_gc_suspend_finalizers (void)
 {
-       return sgen_gather_finalizers_if (object_in_domain_predicate, domain, out_array, out_size);
+       sgen_set_suspend_finalizers ();
 }
 
 /*
@@ -737,18 +771,6 @@ mono_gc_ephemeron_array_add (MonoObject *obj)
  * Appdomain handling
  */
 
-void
-mono_gc_set_current_thread_appdomain (MonoDomain *domain)
-{
-       SgenThreadInfo *info = mono_thread_info_current ();
-
-       /* Could be called from sgen_thread_unregister () with a NULL info */
-       if (domain) {
-               g_assert (info);
-               info->client_info.stopped_domain = domain;
-       }
-}
-
 static gboolean
 need_remove_object_for_domain (GCObject *start, MonoDomain *domain)
 {
@@ -850,7 +872,7 @@ mono_gc_clear_domain (MonoDomain * domain)
        sgen_stop_world (0);
 
        if (sgen_concurrent_collection_in_progress ())
-               sgen_perform_collection (0, GENERATION_OLD, "clear domain", TRUE);
+               sgen_perform_collection (0, GENERATION_OLD, "clear domain", TRUE, FALSE);
        SGEN_ASSERT (0, !sgen_concurrent_collection_in_progress (), "We just ordered a synchronous collection.  Why are we collecting concurrently?");
 
        major_collector.finish_sweeping ();
@@ -907,11 +929,11 @@ mono_gc_clear_domain (MonoDomain * domain)
        major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_PINNED, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain);
 
        if (domain == mono_get_root_domain ()) {
-               sgen_pin_stats_print_class_stats ();
+               sgen_pin_stats_report ();
                sgen_object_layout_dump (stdout);
        }
 
-       sgen_restart_world (0, NULL);
+       sgen_restart_world (0);
 
        binary_protocol_domain_unload_end (domain);
        binary_protocol_flush_buffers (FALSE);
@@ -923,20 +945,12 @@ mono_gc_clear_domain (MonoDomain * domain)
  * Allocation
  */
 
-static gboolean alloc_events = FALSE;
-
-void
-mono_gc_enable_alloc_events (void)
-{
-       alloc_events = TRUE;
-}
-
 void*
 mono_gc_alloc_obj (MonoVTable *vtable, size_t size)
 {
        MonoObject *obj = sgen_alloc_obj (vtable, size);
 
-       if (G_UNLIKELY (alloc_events)) {
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS)) {
                if (obj)
                        mono_profiler_allocation (obj);
        }
@@ -949,7 +963,7 @@ mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size)
 {
        MonoObject *obj = sgen_alloc_obj_pinned (vtable, size);
 
-       if (G_UNLIKELY (alloc_events)) {
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS)) {
                if (obj)
                        mono_profiler_allocation (obj);
        }
@@ -962,7 +976,7 @@ mono_gc_alloc_mature (MonoVTable *vtable, size_t size)
 {
        MonoObject *obj = sgen_alloc_obj_mature (vtable, size);
 
-       if (G_UNLIKELY (alloc_events)) {
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS)) {
                if (obj)
                        mono_profiler_allocation (obj);
        }
@@ -974,11 +988,11 @@ void*
 mono_gc_alloc_fixed (size_t size, MonoGCDescriptor descr, MonoGCRootSource source, const char *msg)
 {
        /* FIXME: do a single allocation */
-       void *res = calloc (1, size);
+       void *res = g_calloc (1, size);
        if (!res)
                return NULL;
        if (!mono_gc_register_root ((char *)res, size, descr, source, msg)) {
-               free (res);
+               g_free (res);
                res = NULL;
        }
        return res;
@@ -988,7 +1002,7 @@ void
 mono_gc_free_fixed (void* addr)
 {
        mono_gc_deregister_root ((char *)addr);
-       free (addr);
+       g_free (addr);
 }
 
 /*
@@ -1001,45 +1015,43 @@ static gboolean use_managed_allocator = TRUE;
 
 #ifdef MANAGED_ALLOCATION
 
-#ifdef HAVE_KW_THREAD
+#if defined(HAVE_KW_THREAD) || defined(TARGET_OSX) || defined(TARGET_WIN32) || defined(TARGET_ANDROID) || defined(TARGET_IOS)
 
-#define EMIT_TLS_ACCESS_NEXT_ADDR(mb)  do {    \
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);   \
-       mono_mb_emit_byte ((mb), CEE_MONO_TLS);         \
-       mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_TLAB_NEXT_ADDR);            \
+// Cache the SgenThreadInfo pointer in a local 'var'.
+#define EMIT_TLS_ACCESS_VAR(mb, var) \
+       do { \
+               var = mono_mb_add_local ((mb), &mono_defaults.int_class->byval_arg); \
+               mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
+               mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
+               mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_THREAD_INFO); \
+               mono_mb_emit_stloc ((mb), (var)); \
        } while (0)
 
-#define EMIT_TLS_ACCESS_TEMP_END(mb)   do {    \
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);   \
-       mono_mb_emit_byte ((mb), CEE_MONO_TLS);         \
-       mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_TLAB_TEMP_END);             \
+#define EMIT_TLS_ACCESS_IN_CRITICAL_REGION_ADDR(mb, var) \
+       do { \
+               mono_mb_emit_ldloc ((mb), (var)); \
+               mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenClientThreadInfo, in_critical_region)); \
+               mono_mb_emit_byte ((mb), CEE_ADD); \
        } while (0)
 
-#else
-
-#if defined(TARGET_OSX) || defined(TARGET_WIN32) || defined(TARGET_ANDROID) || defined(TARGET_IOS)
-#define EMIT_TLS_ACCESS_NEXT_ADDR(mb)  do {    \
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);   \
-       mono_mb_emit_byte ((mb), CEE_MONO_TLS);         \
-       mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_THREAD_INFO);       \
-       mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenThreadInfo, tlab_next_addr));  \
+#define EMIT_TLS_ACCESS_NEXT_ADDR(mb, var)     do {    \
+       mono_mb_emit_ldloc ((mb), (var));               \
+       mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenThreadInfo, tlab_next));       \
        mono_mb_emit_byte ((mb), CEE_ADD);              \
-       mono_mb_emit_byte ((mb), CEE_LDIND_I);          \
        } while (0)
 
-#define EMIT_TLS_ACCESS_TEMP_END(mb)   do {    \
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);   \
-       mono_mb_emit_byte ((mb), CEE_MONO_TLS);         \
-       mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_THREAD_INFO);       \
+#define EMIT_TLS_ACCESS_TEMP_END(mb, var)      do {    \
+       mono_mb_emit_ldloc ((mb), (var));               \
        mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenThreadInfo, tlab_temp_end));   \
        mono_mb_emit_byte ((mb), CEE_ADD);              \
        mono_mb_emit_byte ((mb), CEE_LDIND_I);          \
        } while (0)
 
 #else
-#define EMIT_TLS_ACCESS_NEXT_ADDR(mb)  do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
-#define EMIT_TLS_ACCESS_TEMP_END(mb)   do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
-#endif
+#define EMIT_TLS_ACCESS_VAR(mb, _var)  do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
+#define EMIT_TLS_ACCESS_NEXT_ADDR(mb, _var)    do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
+#define EMIT_TLS_ACCESS_TEMP_END(mb, _var)     do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
+#define EMIT_TLS_ACCESS_IN_CRITICAL_REGION_ADDR(mb, _var)      do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0)
 
 #endif
 
@@ -1052,9 +1064,10 @@ static gboolean use_managed_allocator = TRUE;
  * that they are executed atomically via the restart mechanism.
  */
 static MonoMethod*
-create_allocator (int atype, gboolean slowpath)
+create_allocator (int atype, ManagedAllocatorVariant variant)
 {
-       int p_var, size_var, real_size_var;
+       int p_var, size_var, real_size_var, thread_var G_GNUC_UNUSED;
+       gboolean slowpath = variant == MANAGED_ALLOCATOR_SLOW_PATH;
        guint32 slowpath_branch, max_size_branch;
        MonoMethodBuilder *mb;
        MonoMethod *res;
@@ -1126,6 +1139,12 @@ create_allocator (int atype, gboolean slowpath)
                goto done;
        }
 
+       /*
+        * Tls access might call foreign code or code without jinfo. This can
+        * only happen if we are outside of the critical region.
+        */
+       EMIT_TLS_ACCESS_VAR (mb, thread_var);
+
        size_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
        if (atype == ATYPE_SMALL) {
                /* size_var = size_arg */
@@ -1252,6 +1271,14 @@ create_allocator (int atype, gboolean slowpath)
                g_assert_not_reached ();
        }
 
+#ifdef MANAGED_ALLOCATOR_CAN_USE_CRITICAL_REGION
+       EMIT_TLS_ACCESS_IN_CRITICAL_REGION_ADDR (mb, thread_var);
+       mono_mb_emit_byte (mb, CEE_LDC_I4_1);
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_ATOMIC_STORE_I4);
+       mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_NONE);
+#endif
+
        if (nursery_canaries_enabled ()) {
                real_size_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
                mono_mb_emit_ldloc (mb, size_var);
@@ -1283,7 +1310,7 @@ create_allocator (int atype, gboolean slowpath)
 
        /* tlab_next_addr (local) = tlab_next_addr (TLS var) */
        tlab_next_addr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
-       EMIT_TLS_ACCESS_NEXT_ADDR (mb);
+       EMIT_TLS_ACCESS_NEXT_ADDR (mb, thread_var);
        mono_mb_emit_stloc (mb, tlab_next_addr_var);
 
        /* p = (void**)tlab_next; */
@@ -1307,7 +1334,7 @@ create_allocator (int atype, gboolean slowpath)
 
        /* if (G_LIKELY (new_next < tlab_temp_end)) */
        mono_mb_emit_ldloc (mb, new_next_var);
-       EMIT_TLS_ACCESS_TEMP_END (mb);
+       EMIT_TLS_ACCESS_TEMP_END (mb, thread_var);
        slowpath_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BLT_UN_S);
 
        /* Slowpath */
@@ -1316,6 +1343,18 @@ create_allocator (int atype, gboolean slowpath)
 
        mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
        mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN);
+       /*
+        * We are no longer in a critical section. We need to do this before calling
+        * to unmanaged land in order to avoid stw deadlocks since unmanaged code
+        * might take locks.
+        */
+#ifdef MANAGED_ALLOCATOR_CAN_USE_CRITICAL_REGION
+       EMIT_TLS_ACCESS_IN_CRITICAL_REGION_ADDR (mb, thread_var);
+       mono_mb_emit_byte (mb, CEE_LDC_I4_0);
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_ATOMIC_STORE_I4);
+       mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_NONE);
+#endif
 
        /* FIXME: mono_gc_alloc_obj takes a 'size_t' as an argument, not an int32 */
        mono_mb_emit_ldarg (mb, 0);
@@ -1343,11 +1382,6 @@ create_allocator (int atype, gboolean slowpath)
        mono_mb_emit_ldloc (mb, new_next_var);
        mono_mb_emit_byte (mb, CEE_STIND_I);
 
-       /*The tlab store must be visible before the the vtable store. This could be replaced with a DDS but doing it with IL would be tricky. */
-       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
-       mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER);
-       mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL);
-
        /* *p = vtable; */
        mono_mb_emit_ldloc (mb, p_var);
        mono_mb_emit_ldarg (mb, 0);
@@ -1382,21 +1416,20 @@ create_allocator (int atype, gboolean slowpath)
                mono_mb_emit_byte (mb, MONO_CEE_ADD);
                mono_mb_emit_ldarg (mb, 1);
                mono_mb_emit_byte (mb, MONO_CEE_STIND_I4);
-               /* s->chars [len] = 0; */
-               mono_mb_emit_ldloc (mb, p_var);
-               mono_mb_emit_ldloc (mb, size_var);
-               mono_mb_emit_icon (mb, 2);
-               mono_mb_emit_byte (mb, MONO_CEE_SUB);
-               mono_mb_emit_byte (mb, MONO_CEE_ADD);
-               mono_mb_emit_icon (mb, 0);
-               mono_mb_emit_byte (mb, MONO_CEE_STIND_I2);
        }
 
+#ifdef MANAGED_ALLOCATOR_CAN_USE_CRITICAL_REGION
+       EMIT_TLS_ACCESS_IN_CRITICAL_REGION_ADDR (mb, thread_var);
+       mono_mb_emit_byte (mb, CEE_LDC_I4_0);
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_ATOMIC_STORE_I4);
+#else
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER);
+#endif
        /*
        We must make sure both vtable and max_length are globaly visible before returning to managed land.
        */
-       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
-       mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER);
        mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL);
 
        /* return p */
@@ -1445,17 +1478,19 @@ mono_gc_get_managed_allocator (MonoClass *klass, gboolean for_box, gboolean know
                return NULL;
        if (known_instance_size && ALIGN_TO (klass->instance_size, SGEN_ALLOC_ALIGN) >= SGEN_MAX_SMALL_OBJ_SIZE)
                return NULL;
-       if (mono_class_has_finalizer (klass) || mono_class_is_marshalbyref (klass) || (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS))
+       if (mono_class_has_finalizer (klass) || mono_class_is_marshalbyref (klass))
                return NULL;
        if (klass->rank)
                return NULL;
+       if (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS)
+               return NULL;
        if (klass->byval_arg.type == MONO_TYPE_STRING)
-               return mono_gc_get_managed_allocator_by_type (ATYPE_STRING, FALSE);
+               return mono_gc_get_managed_allocator_by_type (ATYPE_STRING, MANAGED_ALLOCATOR_REGULAR);
        /* Generic classes have dynamic field and can go above MAX_SMALL_OBJ_SIZE. */
        if (known_instance_size)
-               return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL, FALSE);
+               return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL, MANAGED_ALLOCATOR_REGULAR);
        else
-               return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL, FALSE);
+               return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL, MANAGED_ALLOCATOR_REGULAR);
 #else
        return NULL;
 #endif
@@ -1475,7 +1510,7 @@ mono_gc_get_managed_array_allocator (MonoClass *klass)
                return NULL;
        g_assert (!mono_class_has_finalizer (klass) && !mono_class_is_marshalbyref (klass));
 
-       return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR, FALSE);
+       return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR, MANAGED_ALLOCATOR_REGULAR);
 #else
        return NULL;
 #endif
@@ -1488,23 +1523,29 @@ sgen_set_use_managed_allocator (gboolean flag)
 }
 
 MonoMethod*
-mono_gc_get_managed_allocator_by_type (int atype, gboolean slowpath)
+mono_gc_get_managed_allocator_by_type (int atype, ManagedAllocatorVariant variant)
 {
 #ifdef MANAGED_ALLOCATION
        MonoMethod *res;
-       MonoMethod **cache = slowpath ? slowpath_alloc_method_cache : alloc_method_cache;
+       MonoMethod **cache;
 
-       if (!use_managed_allocator)
+       if (variant == MANAGED_ALLOCATOR_REGULAR && !use_managed_allocator)
                return NULL;
 
-       if (!mono_runtime_has_tls_get ())
+       if (variant == MANAGED_ALLOCATOR_REGULAR && !mono_runtime_has_tls_get ())
                return NULL;
 
+       switch (variant) {
+       case MANAGED_ALLOCATOR_REGULAR: cache = alloc_method_cache; break;
+       case MANAGED_ALLOCATOR_SLOW_PATH: cache = slowpath_alloc_method_cache; break;
+       default: g_assert_not_reached (); break;
+       }
+
        res = cache [atype];
        if (res)
                return res;
 
-       res = create_allocator (atype, slowpath);
+       res = create_allocator (atype, variant);
        LOCK_GC;
        if (cache [atype]) {
                mono_free_method (res);
@@ -1613,7 +1654,7 @@ find_next_card (guint8 *card_data, guint8 *end)
 #define ARRAY_OBJ_INDEX(ptr,array,elem_size) (((char*)(ptr) - ((char*)(array) + G_STRUCT_OFFSET (MonoArray, vector))) / (elem_size))
 
 gboolean
-sgen_client_cardtable_scan_object (GCObject *obj, mword block_obj_size, guint8 *cards, gboolean mod_union, ScanCopyContext ctx)
+sgen_client_cardtable_scan_object (GCObject *obj, mword block_obj_size, guint8 *cards, ScanCopyContext ctx)
 {
        MonoVTable *vt = SGEN_LOAD_VTABLE (obj);
        MonoClass *klass = vt->klass;
@@ -1693,20 +1734,11 @@ LOOP_HEAD:
                                for (; elem < card_end; elem += elem_size)
                                        scan_vtype_func (obj, elem, desc, ctx.queue BINARY_PROTOCOL_ARG (elem_size));
                        } else {
-                               CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object;
+                               ScanPtrFieldFunc scan_ptr_field_func = ctx.ops->scan_ptr_field;
 
                                HEAVY_STAT (++los_array_cards);
-                               for (; elem < card_end; elem += SIZEOF_VOID_P) {
-                                       GCObject *new_;
-                                       gpointer old = *(gpointer*)elem;
-                                       if ((mod_union && old) || G_UNLIKELY (sgen_ptr_in_nursery (old))) {
-                                               HEAVY_STAT (++los_array_remsets);
-                                               copy_func ((GCObject**)elem, ctx.queue);
-                                               new_ = *(GCObject **)elem;
-                                               if (G_UNLIKELY (sgen_ptr_in_nursery (new_)))
-                                                       sgen_add_to_global_remset (elem, new_);
-                                       }
-                               }
+                               for (; elem < card_end; elem += SIZEOF_VOID_P)
+                                       scan_ptr_field_func (obj, (GCObject**)elem, ctx.queue);
                        }
 
                        binary_protocol_card_scan (first_elem, elem - first_elem);
@@ -1765,7 +1797,7 @@ mono_gc_alloc_vector (MonoVTable *vtable, size_t size, uintptr_t max_length)
        UNLOCK_GC;
 
  done:
-       if (G_UNLIKELY (alloc_events))
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS))
                mono_profiler_allocation (&arr->obj);
 
        SGEN_ASSERT (6, SGEN_ALIGN_UP (size) == SGEN_ALIGN_UP (sgen_client_par_object_get_size (vtable, (GCObject*)arr)), "Vector has incorrect size.");
@@ -1813,7 +1845,7 @@ mono_gc_alloc_array (MonoVTable *vtable, size_t size, uintptr_t max_length, uint
        UNLOCK_GC;
 
  done:
-       if (G_UNLIKELY (alloc_events))
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS))
                mono_profiler_allocation (&arr->obj);
 
        SGEN_ASSERT (6, SGEN_ALIGN_UP (size) == SGEN_ALIGN_UP (sgen_client_par_object_get_size (vtable, (GCObject*)arr)), "Array has incorrect size.");
@@ -1854,7 +1886,7 @@ mono_gc_alloc_string (MonoVTable *vtable, size_t size, gint32 len)
        UNLOCK_GC;
 
  done:
-       if (G_UNLIKELY (alloc_events))
+       if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS))
                mono_profiler_allocation (&str->object);
 
        return str;
@@ -2073,22 +2105,45 @@ sgen_client_collecting_major_3 (SgenPointerQueue *fin_ready_queue, SgenPointerQu
 static void *moved_objects [MOVED_OBJECTS_NUM];
 static int moved_objects_idx = 0;
 
+static SgenPointerQueue moved_objects_queue = SGEN_POINTER_QUEUE_INIT (INTERNAL_MEM_MOVED_OBJECT);
+
 void
 mono_sgen_register_moved_object (void *obj, void *destination)
 {
-       g_assert (mono_profiler_events & MONO_PROFILE_GC_MOVES);
+       /*
+        * This function can be called from SGen's worker threads. We want to try
+        * and avoid exposing those threads to the profiler API, so queue up move
+        * events and send them later when the main GC thread calls
+        * mono_sgen_gc_event_moves ().
+        *
+        * TODO: Once SGen has multiple worker threads, we need to switch to a
+        * lock-free data structure for the queue as multiple threads will be
+        * adding to it at the same time.
+        */
+       if (sgen_thread_pool_is_thread_pool_thread (mono_native_thread_id_get ())) {
+               sgen_pointer_queue_add (&moved_objects_queue, obj);
+               sgen_pointer_queue_add (&moved_objects_queue, destination);
+       } else {
+               if (moved_objects_idx == MOVED_OBJECTS_NUM) {
+                       mono_profiler_gc_moves (moved_objects, moved_objects_idx);
+                       moved_objects_idx = 0;
+               }
 
-       if (moved_objects_idx == MOVED_OBJECTS_NUM) {
-               mono_profiler_gc_moves (moved_objects, moved_objects_idx);
-               moved_objects_idx = 0;
+               moved_objects [moved_objects_idx++] = obj;
+               moved_objects [moved_objects_idx++] = destination;
        }
-       moved_objects [moved_objects_idx++] = obj;
-       moved_objects [moved_objects_idx++] = destination;
 }
 
 void
 mono_sgen_gc_event_moves (void)
 {
+       while (!sgen_pointer_queue_is_empty (&moved_objects_queue)) {
+               void *dst = sgen_pointer_queue_pop (&moved_objects_queue);
+               void *src = sgen_pointer_queue_pop (&moved_objects_queue);
+
+               mono_sgen_register_moved_object (src, dst);
+       }
+
        if (moved_objects_idx) {
                mono_profiler_gc_moves (moved_objects, moved_objects_idx);
                moved_objects_idx = 0;
@@ -2210,8 +2265,6 @@ sgen_client_thread_register (SgenThreadInfo* info, void *stack_bottom_fallback)
 #endif
 
        info->client_info.skip = 0;
-       info->client_info.stopped_ip = NULL;
-       info->client_info.stopped_domain = NULL;
 
        info->client_info.stack_start = NULL;
 
@@ -2220,12 +2273,9 @@ sgen_client_thread_register (SgenThreadInfo* info, void *stack_bottom_fallback)
        info->client_info.signal = 0;
 #endif
 
-       /* On win32, stack_start_limit should be 0, since the stack can grow dynamically */
        mono_thread_info_get_stack_bounds (&staddr, &stsize);
        if (staddr) {
-#ifndef HOST_WIN32
                info->client_info.stack_start_limit = staddr;
-#endif
                info->client_info.stack_end = staddr + stsize;
        } else {
                gsize stack_bottom = (gsize)stack_bottom_fallback;
@@ -2234,11 +2284,7 @@ sgen_client_thread_register (SgenThreadInfo* info, void *stack_bottom_fallback)
                info->client_info.stack_end = (char*)stack_bottom;
        }
 
-#ifdef USE_MONO_CTX
        memset (&info->client_info.ctx, 0, sizeof (MonoContext));
-#else
-       memset (&info->client_info.regs, 0, sizeof (info->client_info.regs));
-#endif
 
        if (mono_gc_get_gc_callbacks ()->thread_attach_func)
                info->client_info.runtime_data = mono_gc_get_gc_callbacks ()->thread_attach_func ();
@@ -2246,6 +2292,8 @@ sgen_client_thread_register (SgenThreadInfo* info, void *stack_bottom_fallback)
        binary_protocol_thread_register ((gpointer)mono_thread_info_get_tid (info));
 
        SGEN_LOG (3, "registered thread %p (%p) stack end %p", info, (gpointer)mono_thread_info_get_tid (info), info->client_info.stack_end);
+
+       info->client_info.info.handle_stack = mono_handle_stack_alloc ();
 }
 
 void
@@ -2271,6 +2319,10 @@ sgen_client_thread_unregister (SgenThreadInfo *p)
 
        binary_protocol_thread_unregister ((gpointer)tid);
        SGEN_LOG (3, "unregister thread %p (%p)", p, (gpointer)tid);
+
+       HandleStack *handles = (HandleStack*) p->client_info.info.handle_stack;
+       p->client_info.info.handle_stack = NULL;
+       mono_handle_stack_free (handles);
 }
 
 void
@@ -2283,12 +2335,6 @@ mono_gc_set_skip_thread (gboolean skip)
        UNLOCK_GC;
 }
 
-static gboolean
-is_critical_method (MonoMethod *method)
-{
-       return mono_runtime_is_critical_method (method) || sgen_is_critical_method (method);
-}
-
 static gboolean
 thread_in_critical_region (SgenThreadInfo *info)
 {
@@ -2298,8 +2344,6 @@ thread_in_critical_region (SgenThreadInfo *info)
 static void
 sgen_thread_attach (SgenThreadInfo *info)
 {
-       mono_handle_arena_init ((MonoHandleArena**) &info->client_info.info.handle_arena);
-
        if (mono_gc_get_gc_callbacks ()->thread_attach_func && !info->client_info.runtime_data)
                info->client_info.runtime_data = mono_gc_get_gc_callbacks ()->thread_attach_func ();
 }
@@ -2308,15 +2352,13 @@ static void
 sgen_thread_detach (SgenThreadInfo *p)
 {
        /* If a delegate is passed to native code and invoked on a thread we dont
-        * know about, the jit will register it with mono_jit_thread_attach, but
+        * know about, marshal will register it with mono_threads_attach_coop, but
         * we have no way of knowing when that thread goes away.  SGen has a TSD
         * so we assume that if the domain is still registered, we can detach
         * the thread
         */
-       if (mono_domain_get ())
+       if (mono_thread_internal_current_is_attached ())
                mono_thread_detach_internal (mono_thread_internal_current ());
-
-       mono_handle_arena_cleanup ((MonoHandleArena**) &p->client_info.info.handle_arena);
 }
 
 gboolean
@@ -2339,6 +2381,7 @@ void
 sgen_client_thread_register_worker (void)
 {
        mono_thread_info_register_small_id ();
+       mono_native_thread_set_name (mono_native_thread_id_get (), "SGen worker");
 }
 
 /* Variables holding start/end nursery so it won't have to be passed at every call */
@@ -2369,7 +2412,7 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p
 
        FOREACH_THREAD (info) {
                int skip_reason = 0;
-               void *aligned_stack_start = (void*)(mword) ALIGN_TO ((mword)info->client_info.stack_start, SIZEOF_VOID_P);
+               void *aligned_stack_start;
 
                if (info->client_info.skip) {
                        SGEN_LOG (3, "Skipping dead thread %p, range: %p-%p, size: %zd", info, info->client_info.stack_start, info->client_info.stack_end, (char*)info->client_info.stack_end - (char*)info->client_info.stack_start);
@@ -2380,6 +2423,9 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p
                } else if (!mono_thread_info_is_live (info)) {
                        SGEN_LOG (3, "Skipping non-running thread %p, range: %p-%p, size: %zd (state %x)", info, info->client_info.stack_start, info->client_info.stack_end, (char*)info->client_info.stack_end - (char*)info->client_info.stack_start, info->client_info.info.thread_state);
                        skip_reason = 3;
+               } else if (!info->client_info.stack_start) {
+                       SGEN_LOG (3, "Skipping starting or detaching thread %p", info);
+                       skip_reason = 4;
                }
 
                binary_protocol_scan_stack ((gpointer)mono_thread_info_get_tid (info), info->client_info.stack_start, info->client_info.stack_end, skip_reason);
@@ -2387,6 +2433,26 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p
                if (skip_reason)
                        continue;
 
+               g_assert (info->client_info.stack_start);
+               g_assert (info->client_info.stack_end);
+
+               aligned_stack_start = (void*)(mword) ALIGN_TO ((mword)info->client_info.stack_start, SIZEOF_VOID_P);
+#ifdef HOST_WIN32
+               /* Windows uses a guard page before the committed stack memory pages to detect when the
+                  stack needs to be grown. If we suspend a thread just after a function prolog has
+                  decremented the stack pointer to point into the guard page but before the thread has
+                  been able to read or write to that page, starting the stack scan at aligned_stack_start
+                  will raise a STATUS_GUARD_PAGE_VIOLATION and the process will crash. This code uses
+                  VirtualQuery() to determine whether stack_start points into the guard page and then
+                  updates aligned_stack_start to point at the next non-guard page. */
+               MEMORY_BASIC_INFORMATION mem_info;
+               SIZE_T result = VirtualQuery(info->client_info.stack_start, &mem_info, sizeof(mem_info));
+               g_assert (result != 0);
+               if (mem_info.Protect & PAGE_GUARD) {
+                       aligned_stack_start = ((char*) mem_info.BaseAddress) + mem_info.RegionSize;
+               }
+#endif
+
                g_assert (info->client_info.suspend_done);
                SGEN_LOG (3, "Scanning thread %p, range: %p-%p, size: %zd, pinned=%zd", info, info->client_info.stack_start, info->client_info.stack_end, (char*)info->client_info.stack_end - (char*)info->client_info.stack_start, sgen_get_pinned_count ());
                if (mono_gc_get_gc_callbacks ()->thread_mark_func && !conservative_stack_mark) {
@@ -2396,20 +2462,18 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p
                                fprintf (stderr, "Precise stack mark not supported - disabling.\n");
                                conservative_stack_mark = TRUE;
                        }
+                       //FIXME we should eventually use the new stack_mark from coop
                        sgen_conservatively_pin_objects_from ((void **)aligned_stack_start, (void **)info->client_info.stack_end, start_nursery, end_nursery, PIN_TYPE_STACK);
                }
 
                if (!precise) {
-#ifdef USE_MONO_CTX
                        sgen_conservatively_pin_objects_from ((void**)&info->client_info.ctx, (void**)(&info->client_info.ctx + 1),
                                start_nursery, end_nursery, PIN_TYPE_STACK);
-#else
-                       sgen_conservatively_pin_objects_from ((void**)&info->client_info.regs, (void**)&info->client_info.regs + ARCH_NUM_REGS,
-                                       start_nursery, end_nursery, PIN_TYPE_STACK);
-#endif
+
                        {
                                // This is used on Coop GC for platforms where we cannot get the data for individual registers.
                                // We force a spill of all registers into the stack and pass a chunk of data into sgen.
+                               //FIXME under coop, for now, what we need to ensure is that we scan any extra memory from info->client_info.stack_end to stack_mark
                                MonoThreadUnwindState *state = &info->client_info.info.thread_saved_state [SELF_SUSPEND_STATE_INDEX];
                                if (state && state->gc_stackdata) {
                                        sgen_conservatively_pin_objects_from ((void **)state->gc_stackdata, (void**)((char*)state->gc_stackdata + state->gc_stackdata_size),
@@ -2417,6 +2481,9 @@ sgen_client_scan_thread_data (void *start_nursery, void *end_nursery, gboolean p
                                }
                        }
                }
+               if (precise && info->client_info.info.handle_stack) {
+                       mono_handle_stack_scan ((HandleStack*)info->client_info.info.handle_stack, (GcScanFunc)ctx.ops->copy_or_mark_object, ctx.queue);
+               }
        } FOREACH_THREAD_END
 }
 
@@ -2504,11 +2571,6 @@ mono_gc_get_generation (MonoObject *obj)
        return 1;
 }
 
-void
-mono_gc_enable_events (void)
-{
-}
-
 const char *
 mono_gc_get_gc_name (void)
 {
@@ -2518,7 +2580,11 @@ mono_gc_get_gc_name (void)
 char*
 mono_gc_get_description (void)
 {
+#ifdef HAVE_CONC_GC_AS_DEFAULT
+       return g_strdup ("sgen (concurrent by default)");
+#else
        return g_strdup ("sgen");
+#endif
 }
 
 void
@@ -2767,37 +2833,6 @@ sgen_client_degraded_allocation (size_t size)
        }
 }
 
-void
-sgen_client_log_timing (GGTimingInfo *info, mword last_major_num_sections, mword last_los_memory_usage)
-{
-       SgenMajorCollector *major_collector = sgen_get_major_collector ();
-       mword num_major_sections = major_collector->get_num_major_sections ();
-       char full_timing_buff [1024];
-       full_timing_buff [0] = '\0';
-
-       if (!info->is_overflow)
-               sprintf (full_timing_buff, "total %.2fms, bridge %.2fms", info->stw_time / 10000.0f, (int)info->bridge_time / 10000.0f);
-       if (info->generation == GENERATION_OLD)
-               mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MAJOR%s: (%s) pause %.2fms, %s major %dK/%dK los %dK/%dK",
-                       info->is_overflow ? "_OVERFLOW" : "",
-                       info->reason ? info->reason : "",
-                       (int)info->total_time / 10000.0f,
-                       full_timing_buff,
-                       major_collector->section_size * num_major_sections / 1024,
-                       major_collector->section_size * last_major_num_sections / 1024,
-                       los_memory_usage / 1024,
-                       last_los_memory_usage / 1024);
-       else
-               mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MINOR%s: (%s) pause %.2fms, %s promoted %dK major %dK los %dK",
-                               info->is_overflow ? "_OVERFLOW" : "",
-                       info->reason ? info->reason : "",
-                       (int)info->total_time / 10000.0f,
-                       full_timing_buff,
-                       (num_major_sections - last_major_num_sections) * major_collector->section_size / 1024,
-                       major_collector->section_size * num_major_sections / 1024,
-                       los_memory_usage / 1024);
-}
-
 /*
  * Debugging
  */
@@ -2807,6 +2842,7 @@ sgen_client_description_for_internal_mem_type (int type)
 {
        switch (type) {
        case INTERNAL_MEM_EPHEMERON_LINK: return "ephemeron-link";
+       case INTERNAL_MEM_MOVED_OBJECT: return "moved-object";
        default:
                return NULL;
        }
@@ -2853,8 +2889,8 @@ sgen_client_init (void)
        cb.thread_detach = sgen_thread_detach;
        cb.thread_unregister = sgen_thread_unregister;
        cb.thread_attach = sgen_thread_attach;
-       cb.mono_method_is_critical = (gboolean (*)(void *))is_critical_method;
        cb.mono_thread_in_critical_region = thread_in_critical_region;
+       cb.ip_in_critical_region = ip_in_critical_region;
 
        mono_threads_init (&cb, sizeof (SgenThreadInfo));
 
@@ -2883,13 +2919,6 @@ sgen_client_init (void)
        }
 #endif
 
-       /*
-        * This needs to happen before any internal allocations because
-        * it inits the small id which is required for hazard pointer
-        * operations.
-        */
-       sgen_os_init ();
-
        mono_gc_register_thread (&dummy);
 }
 
@@ -2912,7 +2941,7 @@ sgen_client_handle_gc_param (const char *opt)
        } else if (g_str_has_prefix (opt, "toggleref-test")) {
                /* FIXME: This should probably in MONO_GC_DEBUG */
                sgen_register_test_toggleref_callback ();
-       } else {
+       } else if (!sgen_bridge_handle_gc_param (opt)) {
                return FALSE;
        }
        return TRUE;
@@ -2979,11 +3008,20 @@ sgen_client_describe_invalid_pointer (GCObject *ptr)
        sgen_bridge_describe_pointer (ptr);
 }
 
+static gboolean gc_inited;
+
 void
 mono_gc_base_init (void)
 {
+       if (gc_inited)
+               return;
+
        mono_counters_init ();
 
+#ifndef HOST_WIN32
+       mono_w32handle_init ();
+#endif
+
 #ifdef HEAVY_STATISTICS
        mono_counters_register ("los marked cards", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &los_marked_cards);
        mono_counters_register ("los array cards scanned ", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &los_array_cards);
@@ -2998,14 +3036,20 @@ mono_gc_base_init (void)
 
 #if defined(HAVE_KW_THREAD)
        /* This can happen with using libmonosgen.so */
-       if (mono_tls_key_get_offset (TLS_KEY_SGEN_TLAB_NEXT_ADDR) == -1)
+       if (mono_tls_key_get_offset (TLS_KEY_SGEN_THREAD_INFO) == -1)
                sgen_set_use_managed_allocator (FALSE);
 #endif
+
+       gc_inited = TRUE;
 }
 
 void
 mono_gc_base_cleanup (void)
 {
+       sgen_thread_pool_shutdown ();
+
+       // We should have consumed any outstanding moves.
+       g_assert (sgen_pointer_queue_is_empty (&moved_objects_queue));
 }
 
 gboolean