Merge pull request #2448 from BrzVlad/feature-cprop-opt
[mono.git] / mono / sgen / sgen-gchandles.c
index 355ba380660f8bd731059f229b03da27531c131b..f4b352eac1185d6befd2f9fb5ac8d440d0376a3c 100644 (file)
@@ -25,8 +25,8 @@
 #include "mono/utils/mono-membar.h"
 
 #ifdef HEAVY_STATISTICS
-static volatile guint64 stat_gc_handles_allocated = 0;
-static volatile guint64 stat_gc_handles_max_allocated = 0;
+static volatile guint32 stat_gc_handles_allocated = 0;
+static volatile guint32 stat_gc_handles_max_allocated = 0;
 #endif
 
 #define BUCKETS (32 - MONO_GC_HANDLE_TYPE_SHIFT)
@@ -65,6 +65,7 @@ typedef struct {
        volatile gpointer *volatile entries [BUCKETS];
        volatile guint32 capacity;
        volatile guint32 slot_hint;
+       volatile guint32 max_index;
        guint8 type;
 } HandleData;
 
@@ -100,12 +101,39 @@ bucketize (guint index, guint *bucket, guint *offset)
        *offset = index - bucket_size (*bucket) + MIN_BUCKET_SIZE;
 }
 
-static inline gboolean
+static void
+protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
+{
+       gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
+       gboolean new_ = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
+       gboolean track = handle_type == HANDLE_WEAK_TRACK;
+
+       if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
+               return;
+
+       if (!old && new_)
+               binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
+       else if (old && !new_)
+               binary_protocol_dislink_remove (link, track);
+       else if (old && new_ && old_value != new_value)
+               binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
+}
+
+/* Returns the new value in the slot, or NULL if the CAS failed. */
+static inline gpointer
 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
 {
+       gpointer new_;
        if (obj)
-               return InterlockedCompareExchangePointer (slot, MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type)), old) == old;
-       return InterlockedCompareExchangePointer (slot, MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type)), old) == old;
+               new_ = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
+       else
+               new_ = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
+       SGEN_ASSERT (0, new_, "Why is the occupied bit not set?");
+       if (InterlockedCompareExchangePointer (slot, new_, old) == old) {
+               protocol_gchandle_update (type, (gpointer)slot, old, new_);
+               return new_;
+       }
+       return NULL;
 }
 
 /* Try to claim a slot by setting its occupied bit. */
@@ -115,25 +143,20 @@ try_occupy_slot (HandleData *handles, guint bucket, guint offset, GCObject *obj,
        volatile gpointer *link_addr = &(handles->entries [bucket] [offset]);
        if (MONO_GC_HANDLE_OCCUPIED (*link_addr))
                return FALSE;
-       return try_set_slot (link_addr, obj, NULL, handles->type);
+       return try_set_slot (link_addr, obj, NULL, (GCHandleType)handles->type) != NULL;
 }
 
-#define EMPTY_HANDLE_DATA(type) { { NULL }, 0, 0, (type) }
-
-/* weak and weak-track arrays will be allocated in malloc memory 
- */
 static HandleData gc_handles [] = {
-       EMPTY_HANDLE_DATA (HANDLE_WEAK),
-       EMPTY_HANDLE_DATA (HANDLE_WEAK_TRACK),
-       EMPTY_HANDLE_DATA (HANDLE_NORMAL),
-       EMPTY_HANDLE_DATA (HANDLE_PINNED)
+       { { NULL }, 0, 0, 0, (HANDLE_WEAK) },
+       { { NULL }, 0, 0, 0, (HANDLE_WEAK_TRACK) },
+       { { NULL }, 0, 0, 0, (HANDLE_NORMAL) },
+       { { NULL }, 0, 0, 0, (HANDLE_PINNED) }
 };
 
 static HandleData *
 gc_handles_for_type (GCHandleType type)
 {
-       g_assert (type < HANDLE_TYPE_MAX);
-       return &gc_handles [type];
+       return type < HANDLE_TYPE_MAX ? &gc_handles [type] : NULL;
 }
 
 /* This assumes that the world is stopped. */
@@ -143,12 +166,19 @@ sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_da
        HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
        size_t bucket, offset;
        const guint max_bucket = index_bucket (handles->capacity);
+       guint32 index = 0;
+       const guint32 max_index = handles->max_index;
        for (bucket = 0; bucket < max_bucket; ++bucket) {
                volatile gpointer *entries = handles->entries [bucket];
-               for (offset = 0; offset < bucket_size (bucket); ++offset) {
-                       volatile gpointer *entry = &entries [offset];
-                       gpointer hidden = *entry;
-                       gpointer revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
+               for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
+                       volatile gpointer *entry;
+                       gpointer hidden, revealed;
+                       /* No need to iterate beyond the largest index ever allocated. */
+                       if (index > max_index)
+                               return;
+                       entry = &entries [offset];
+                       hidden = *entry;
+                       revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
                        if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
                                continue;
                        mark_func ((MonoObject **)&revealed, gc_data);
@@ -186,14 +216,20 @@ handle_data_grow (HandleData *handles, guint32 old_capacity)
        const size_t new_bucket_size = sizeof (**handles->entries) * growth;
        if (handles->capacity >= new_capacity)
                return;
-       entries = g_malloc0 (new_bucket_size);
+       entries = (gpointer *)g_malloc0 (new_bucket_size);
        if (handles->type == HANDLE_PINNED)
                sgen_register_root ((char *)entries, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
+       /* The zeroing of the newly allocated bucket must be complete before storing
+        * the new bucket pointer.
+        */
+       mono_memory_write_barrier ();
        if (InterlockedCompareExchangePointer ((volatile gpointer *)&handles->entries [new_bucket], entries, NULL) == NULL) {
+               /* It must not be the case that we succeeded in setting the bucket
+                * pointer, while someone else succeeded in changing the capacity.
+                */
                if (InterlockedCompareExchange ((volatile gint32 *)&handles->capacity, new_capacity, old_capacity) != old_capacity)
                        g_assert_not_reached ();
                handles->slot_hint = old_capacity;
-               mono_memory_write_barrier ();
                return;
        }
        /* Someone beat us to the allocation. */
@@ -210,6 +246,7 @@ alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
        guint bucket, offset;
        guint32 capacity;
        guint32 slot_hint;
+       guint32 max_index;
        if (!handles->capacity)
                handle_data_grow (handles, 0);
 retry:
@@ -223,16 +260,33 @@ retry:
                goto retry;
        }
        handles->slot_hint = index;
+
+       /*
+        * If a GC happens shortly after a new bucket is allocated, the entire
+        * bucket could be scanned even though it's mostly empty. To avoid this,
+        * we track the maximum index seen so far, so that we can skip the empty
+        * slots.
+        *
+        * Note that we update `max_index` before we even try occupying the
+        * slot.  If we did it the other way around and a GC happened in
+        * between, the GC wouldn't know that the slot was occupied.  This is
+        * not a huge deal since `obj` is on the stack and thus pinned anyway,
+        * but hopefully some day it won't be anymore.
+        */
+       do {
+               max_index = handles->max_index;
+               if (index <= max_index)
+                       break;
+       } while (InterlockedCompareExchange ((volatile gint32 *)&handles->max_index, index, max_index) != max_index);
+
        bucketize (index, &bucket, &offset);
        if (!try_occupy_slot (handles, bucket, offset, obj, track))
                goto retry;
 #ifdef HEAVY_STATISTICS
-       InterlockedIncrement64 ((volatile gint64 *)&stat_gc_handles_allocated);
+       InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
        if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
                stat_gc_handles_max_allocated = stat_gc_handles_allocated;
 #endif
-       if (obj && MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type))
-               binary_protocol_dislink_add ((gpointer)&handles->entries [bucket] [offset], obj, track);
        /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
        mono_memory_write_barrier ();
        res = MONO_GC_HANDLE (index, handles->type);
@@ -251,34 +305,38 @@ object_older_than (GCObject *object, int generation)
  * This assumes that the world is stopped!
  */
 void
-sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, gpointer callback(gpointer, GCHandleType, int, gpointer), gpointer user)
+sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
 {
        HandleData *handle_data = gc_handles_for_type (handle_type);
        size_t bucket, offset;
        guint max_bucket = index_bucket (handle_data->capacity);
+       guint32 index = 0;
+       guint32 max_index = handle_data->max_index;
        /* If a new bucket has been allocated, but the capacity has not yet been
         * increased, nothing can yet have been allocated in the bucket because the
         * world is stopped, so we shouldn't miss any handles during iteration.
         */
        for (bucket = 0; bucket < max_bucket; ++bucket) {
                volatile gpointer *entries = handle_data->entries [bucket];
-               for (offset = 0; offset < bucket_size (bucket); ++offset) {
-                       gpointer hidden = entries [offset];
+               for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
+                       gpointer hidden;
                        gpointer result;
                        /* Table must contain no garbage pointers. */
-                       gboolean occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
+                       gboolean occupied;
+                       /* No need to iterate beyond the largest index ever allocated. */
+                       if (index > max_index)
+                                       return;
+                       hidden = entries [offset];
+                       occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
                        g_assert (hidden ? occupied : !occupied);
-                       if (!occupied) // || !MONO_GC_HANDLE_VALID (hidden))
+                       if (!occupied)
                                continue;
                        result = callback (hidden, handle_type, max_generation, user);
-                       if (result) {
+                       if (result)
                                SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
-                               // FIXME: add the dislink_update protocol call here
-                       } else {
-                               // FIXME: enable this for weak links
-                               //binary_protocol_dislink_remove ((gpointer)&handles->entries [bucket] [offset], handles->type == HANDLE_WEAK_TRACK);
-                               HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
-                       }
+                       else
+                               HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
+                       protocol_gchandle_update (handle_type, (gpointer)&entries [offset], hidden, result);
                        entries [offset] = result;
                }
        }
@@ -362,7 +420,7 @@ retry:
         * register, so we need to prevent this from being reordered
         * wrt the check.
         */
-       mono_gc_dummy_use (obj);
+       sgen_dummy_use (obj);
        mono_memory_barrier ();
 
        if (is_weak)
@@ -388,8 +446,11 @@ GCObject*
 mono_gchandle_get_target (guint32 gchandle)
 {
        guint index = MONO_GC_HANDLE_SLOT (gchandle);
-       guint type = MONO_GC_HANDLE_TYPE (gchandle);
+       GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
        HandleData *handles = gc_handles_for_type (type);
+       /* Invalid handles are possible; accessing one should produce NULL. (#34276) */
+       if (!handles)
+               return NULL;
        guint bucket, offset;
        g_assert (index < handles->capacity);
        bucketize (index, &bucket, &offset);
@@ -400,24 +461,20 @@ void
 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
 {
        guint index = MONO_GC_HANDLE_SLOT (gchandle);
-       guint type = MONO_GC_HANDLE_TYPE (gchandle);
+       GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
        HandleData *handles = gc_handles_for_type (type);
-       gboolean track = handles->type == HANDLE_WEAK_TRACK;
+       if (!handles)
+               return;
        guint bucket, offset;
        gpointer slot;
 
        g_assert (index < handles->capacity);
        bucketize (index, &bucket, &offset);
 
-retry:
-       slot = handles->entries [bucket] [offset];
-       g_assert (MONO_GC_HANDLE_OCCUPIED (slot));
-       if (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type)))
-               goto retry;
-       if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot))
-               binary_protocol_dislink_remove ((gpointer)&handles->entries [bucket] [offset], track);
-       if (obj)
-               binary_protocol_dislink_add ((gpointer)&handles->entries [bucket] [offset], obj, track);
+       do {
+               slot = handles->entries [bucket] [offset];
+               SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (slot), "Why are we setting the target on an unoccupied slot?");
+       } while (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, (GCHandleType)handles->type));
 }
 
 static gpointer
@@ -430,9 +487,9 @@ retry:
        if (!MONO_GC_HANDLE_OCCUPIED (slot))
                return NULL;
        if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot)) {
-               GCObject *obj = MONO_GC_REVEAL_POINTER (slot, is_weak);
+               GCObject *obj = (GCObject *)MONO_GC_REVEAL_POINTER (slot, is_weak);
                /* See note [dummy use]. */
-               mono_gc_dummy_use (obj);
+               sgen_dummy_use (obj);
                /*
                 * FIXME: The compiler could technically not carry a reference to obj around
                 * at this point and recompute it later, in which case we would still use
@@ -444,7 +501,7 @@ retry:
        }
        metadata = MONO_GC_REVEAL_POINTER (slot, is_weak);
        /* See note [dummy use]. */
-       mono_gc_dummy_use (metadata);
+       sgen_dummy_use (metadata);
        if (*slot_addr != slot)
                goto retry;
        return metadata;
@@ -454,8 +511,10 @@ gpointer
 sgen_gchandle_get_metadata (guint32 gchandle)
 {
        guint index = MONO_GC_HANDLE_SLOT (gchandle);
-       guint type = MONO_GC_HANDLE_TYPE (gchandle);
+       GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
        HandleData *handles = gc_handles_for_type (type);
+       if (!handles)
+               return NULL;
        guint bucket, offset;
        if (index >= handles->capacity)
                return NULL;
@@ -475,15 +534,18 @@ void
 mono_gchandle_free (guint32 gchandle)
 {
        guint index = MONO_GC_HANDLE_SLOT (gchandle);
-       guint type = MONO_GC_HANDLE_TYPE (gchandle);
+       GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
        HandleData *handles = gc_handles_for_type (type);
+       if (!handles)
+               return;
        guint bucket, offset;
+       gpointer slot;
        bucketize (index, &bucket, &offset);
-       if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (handles->entries [bucket] [offset])) {
-               if (MONO_GC_HANDLE_TYPE_IS_WEAK (handles->type))
-                       binary_protocol_dislink_remove ((gpointer)&handles->entries [bucket] [offset], handles->type == HANDLE_WEAK_TRACK);
+       slot = handles->entries [bucket] [offset];
+       if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (slot)) {
                handles->entries [bucket] [offset] = NULL;
-               HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
+               protocol_gchandle_update (handles->type, (gpointer)&handles->entries [bucket] [offset], slot, NULL);
+               HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
        } else {
                /* print a warning? */
        }
@@ -504,7 +566,7 @@ null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_gener
        if (!MONO_GC_HANDLE_VALID (hidden))
                return hidden;
 
-       obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
+       obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
        SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
 
        if (object_older_than (obj, max_generation))
@@ -539,14 +601,13 @@ typedef struct {
 static gpointer
 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
 {
-       /* Strictly speaking, function pointers are not guaranteed to have the same size as data pointers. */
        WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
        GCObject *obj;
 
        if (!MONO_GC_HANDLE_VALID (hidden))
                return hidden;
 
-       obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
+       obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
        SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
 
        if (object_older_than (obj, max_generation))
@@ -570,8 +631,8 @@ void
 sgen_init_gchandles (void)
 {
 #ifdef HEAVY_STATISTICS
-       mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_allocated);
-       mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_max_allocated);
+       mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
+       mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);
 #endif
 }