[runtime] Only apply the maverick stack size hack added by d6673ca8ec854f291eb32c0484...
[mono.git] / mono / utils / hazard-pointer.c
index e808d9e1102246649c568da8d500c96301157c60..b412e77d7d535ed088203c81a839caa74c545133 100644 (file)
 
 #include <config.h>
 
-#include <mono/metadata/class-internals.h>
 #include <mono/utils/hazard-pointer.h>
 #include <mono/utils/mono-membar.h>
+#include <mono/utils/mono-memory-model.h>
 #include <mono/utils/mono-mmap.h>
 #include <mono/utils/monobitset.h>
 #include <mono/utils/mono-threads.h>
+#include <mono/utils/lock-free-array-queue.h>
+#include <mono/utils/mono-counters.h>
+#include <mono/utils/atomic.h>
 #include <mono/io-layer/io-layer.h>
 
 typedef struct {
        gpointer p;
        MonoHazardousFreeFunc free_func;
+       gboolean might_lock;
 } DelayedFreeItem;
 
-enum {
-       DFE_STATE_FREE,
-       DFE_STATE_USED,
-       DFE_STATE_BUSY
-};
-
-typedef struct {
-       gint32 state;
-       DelayedFreeItem item;
-} DelayedFreeEntry;
-
-typedef struct _DelayedFreeChunk DelayedFreeChunk;
-struct _DelayedFreeChunk {
-       DelayedFreeChunk *next;
-       gint32 num_entries;
-       DelayedFreeEntry entries [MONO_ZERO_LEN_ARRAY];
-};
-
 /* The hazard table */
 #if MONO_SMALL_CONFIG
 #define HAZARD_TABLE_MAX_SIZE  256
+#define HAZARD_TABLE_OVERFLOW  4
 #else
 #define HAZARD_TABLE_MAX_SIZE  16384 /* There cannot be more threads than this number. */
+#define HAZARD_TABLE_OVERFLOW  64
 #endif
 
 static volatile int hazard_table_size = 0;
 static MonoThreadHazardPointers * volatile hazard_table = NULL;
 
+/*
+ * Each entry is either 0 or 1, indicating whether that overflow small
+ * ID is busy.
+ */
+static volatile gint32 overflow_busy [HAZARD_TABLE_OVERFLOW];
+
 /* The table where we keep pointers to blocks to be freed but that
    have to wait because they're guarded by a hazard pointer. */
-static volatile gint32 num_used_delayed_free_entries;
-static DelayedFreeChunk *delayed_free_chunk_list;
+static MonoLockFreeArrayQueue delayed_free_queue = MONO_LOCK_FREE_ARRAY_QUEUE_INIT (sizeof (DelayedFreeItem));
 
 /* The table for small ID assignment */
 static CRITICAL_SECTION small_id_mutex;
 static int small_id_next;
 static int highest_small_id = -1;
 static MonoBitSet *small_id_table;
-
-/*
- * Delayed free table
- *
- * The table is a linked list of arrays (chunks).  Chunks are never
- * removed from the list, only added to the end, in a lock-free manner.
- *
- * Adding or removing an entry in the table is only possible at the end.
- * To do so, the thread first has to increment or decrement
- * num_used_delayed_free_entries.  The entry thus added or removed now
- * "belongs" to that thread.  It first CASes the state to BUSY,
- * writes/reads the entry, and then sets the state to USED or FREE.
- *
- * Note that it's possible that there is contention.  Some thread will
- * always make progress, though.
- *
- * The simplest case of contention is one thread pushing and another
- * thread popping the same entry.  The state is FREE at first, so the
- * pushing thread succeeds in setting it to BUSY.  The popping thread
- * will only succeed with its CAS once the state is USED, which is the
- * case after the pushing thread has finished pushing.
- */
-
-static DelayedFreeChunk*
-alloc_delayed_free_chunk (void)
-{
-       int size = mono_pagesize ();
-       int num_entries = (size - (sizeof (DelayedFreeChunk) - sizeof (DelayedFreeEntry) * MONO_ZERO_LEN_ARRAY)) / sizeof (DelayedFreeEntry);
-       DelayedFreeChunk *chunk = mono_valloc (0, size, MONO_MMAP_READ | MONO_MMAP_WRITE);
-       chunk->num_entries = num_entries;
-       return chunk;
-}
-
-static void
-free_delayed_free_chunk (DelayedFreeChunk *chunk)
-{
-       mono_vfree (chunk, mono_pagesize ());
-}
-
-static DelayedFreeEntry*
-get_delayed_free_entry (int index)
-{
-       DelayedFreeChunk *chunk;
-
-       g_assert (index >= 0);
-
-       if (!delayed_free_chunk_list) {
-               chunk = alloc_delayed_free_chunk ();
-               mono_memory_write_barrier ();
-               if (InterlockedCompareExchangePointer ((volatile gpointer *)&delayed_free_chunk_list, chunk, NULL) != NULL)
-                       free_delayed_free_chunk (chunk);
-       }
-
-       chunk = delayed_free_chunk_list;
-       g_assert (chunk);
-
-       while (index >= chunk->num_entries) {
-               DelayedFreeChunk *next = chunk->next;
-               if (!next) {
-                       next = alloc_delayed_free_chunk ();
-                       mono_memory_write_barrier ();
-                       if (InterlockedCompareExchangePointer ((volatile gpointer *) &chunk->next, next, NULL) != NULL) {
-                               free_delayed_free_chunk (next);
-                               next = chunk->next;
-                               g_assert (next);
-                       }
-               }
-               index -= chunk->num_entries;
-               chunk = next;
-       }
-
-       return &chunk->entries [index];
-}
-
-static void
-delayed_free_push (DelayedFreeItem item)
-{
-       int index, num_used;
-       DelayedFreeEntry *entry;
-
-       do {
-               index = InterlockedIncrement (&num_used_delayed_free_entries) - 1;
-               entry = get_delayed_free_entry (index);
-       } while (InterlockedCompareExchange (&entry->state, DFE_STATE_BUSY, DFE_STATE_FREE) != DFE_STATE_FREE);
-
-       mono_memory_write_barrier ();
-
-       entry->item = item;
-
-       mono_memory_write_barrier ();
-
-       entry->state = DFE_STATE_USED;
-
-       mono_memory_write_barrier ();
-
-       do {
-               num_used = num_used_delayed_free_entries;
-               if (num_used > index)
-                       break;
-       } while (InterlockedCompareExchange (&num_used_delayed_free_entries, index + 1, num_used) != num_used);
-}
-
-static gboolean
-delayed_free_pop (DelayedFreeItem *item)
-{
-       int index;
-       DelayedFreeEntry *entry;
-
-       do {
-               do {
-                       index = num_used_delayed_free_entries;
-                       if (index == 0)
-                               return FALSE;
-               } while (InterlockedCompareExchange (&num_used_delayed_free_entries, index - 1, index) != index);
-
-               entry = get_delayed_free_entry (index - 1);
-       } while (InterlockedCompareExchange (&entry->state, DFE_STATE_BUSY, DFE_STATE_USED) != DFE_STATE_USED);
-
-       mono_memory_barrier ();
-
-       *item = entry->item;
-
-       mono_memory_barrier ();
-
-       entry->state = DFE_STATE_FREE;
-
-       return TRUE;
-}
+static int hazardous_pointer_count;
 
 /*
  * Allocate a small thread id.
@@ -202,7 +68,7 @@ mono_thread_small_id_alloc (void)
        if (!small_id_table)
                small_id_table = mono_bitset_new (1, 0);
 
-       id = mono_bitset_find_first_unset (small_id_table, small_id_next);
+       id = mono_bitset_find_first_unset (small_id_table, small_id_next - 1);
        if (id == -1)
                id = mono_bitset_find_first_unset (small_id_table, -1);
 
@@ -280,15 +146,17 @@ mono_thread_small_id_free (int id)
 static gboolean
 is_pointer_hazardous (gpointer p)
 {
-       int i;
+       int i, j;
        int highest = highest_small_id;
 
        g_assert (highest < hazard_table_size);
 
        for (i = 0; i <= highest; ++i) {
-               if (hazard_table [i].hazard_pointers [0] == p
-                               || hazard_table [i].hazard_pointers [1] == p)
-                       return TRUE;
+               for (j = 0; j < HAZARD_POINTER_COUNT; ++j) {
+                       if (hazard_table [i].hazard_pointers [j] == p)
+                               return TRUE;
+                       LOAD_LOAD_FENCE;
+               }
        }
 
        return FALSE;
@@ -338,17 +206,88 @@ get_hazardous_pointer (gpointer volatile *pp, MonoThreadHazardPointers *hp, int
        return p;
 }
 
+int
+mono_hazard_pointer_save_for_signal_handler (void)
+{
+       int small_id, i;
+       MonoThreadHazardPointers *hp = mono_hazard_pointer_get ();
+       MonoThreadHazardPointers *hp_overflow;
+
+       for (i = 0; i < HAZARD_POINTER_COUNT; ++i)
+               if (hp->hazard_pointers [i])
+                       goto search;
+       return -1;
+
+ search:
+       for (small_id = 0; small_id < HAZARD_TABLE_OVERFLOW; ++small_id) {
+               if (!overflow_busy [small_id])
+                       break;
+       }
+
+       /*
+        * If this assert fails we don't have enough overflow slots.
+        * We should contemplate adding them dynamically.  If we can
+        * make mono_thread_small_id_alloc() lock-free we can just
+        * allocate them on-demand.
+        */
+       g_assert (small_id < HAZARD_TABLE_OVERFLOW);
+
+       if (InterlockedCompareExchange (&overflow_busy [small_id], 1, 0) != 0)
+               goto search;
+
+       hp_overflow = &hazard_table [small_id];
+
+       for (i = 0; i < HAZARD_POINTER_COUNT; ++i)
+               g_assert (!hp_overflow->hazard_pointers [i]);
+       *hp_overflow = *hp;
+
+       mono_memory_write_barrier ();
+
+       memset (hp, 0, sizeof (MonoThreadHazardPointers));
+
+       return small_id;
+}
+
+void
+mono_hazard_pointer_restore_for_signal_handler (int small_id)
+{
+       MonoThreadHazardPointers *hp = mono_hazard_pointer_get ();
+       MonoThreadHazardPointers *hp_overflow;
+       int i;
+
+       if (small_id < 0)
+               return;
+
+       g_assert (small_id < HAZARD_TABLE_OVERFLOW);
+       g_assert (overflow_busy [small_id]);
+
+       for (i = 0; i < HAZARD_POINTER_COUNT; ++i)
+               g_assert (!hp->hazard_pointers [i]);
+
+       hp_overflow = &hazard_table [small_id];
+
+       *hp = *hp_overflow;
+
+       mono_memory_write_barrier ();
+
+       memset (hp_overflow, 0, sizeof (MonoThreadHazardPointers));
+
+       mono_memory_write_barrier ();
+
+       overflow_busy [small_id] = 0;
+}
+
 static gboolean
-try_free_delayed_free_item (void)
+try_free_delayed_free_item (gboolean lock_free_context)
 {
        DelayedFreeItem item;
-       gboolean popped = delayed_free_pop (&item);
+       gboolean popped = mono_lock_free_array_queue_pop (&delayed_free_queue, &item);
 
        if (!popped)
                return FALSE;
 
-       if (is_pointer_hazardous (item.p)) {
-               delayed_free_push (item);
+       if ((lock_free_context && item.might_lock) || (is_pointer_hazardous (item.p))) {
+               mono_lock_free_array_queue_push (&delayed_free_queue, &item);
                return FALSE;
        }
 
@@ -358,23 +297,29 @@ try_free_delayed_free_item (void)
 }
 
 void
-mono_thread_hazardous_free_or_queue (gpointer p, MonoHazardousFreeFunc free_func)
+mono_thread_hazardous_free_or_queue (gpointer p, MonoHazardousFreeFunc free_func,
+               gboolean free_func_might_lock, gboolean lock_free_context)
 {
        int i;
 
+       if (lock_free_context)
+               g_assert (!free_func_might_lock);
+       if (free_func_might_lock)
+               g_assert (!lock_free_context);
+
        /* First try to free a few entries in the delayed free
           table. */
        for (i = 0; i < 3; ++i)
-               try_free_delayed_free_item ();
+               try_free_delayed_free_item (lock_free_context);
 
        /* Now see if the pointer we're freeing is hazardous.  If it
           isn't, free it.  Otherwise put it in the delay list. */
        if (is_pointer_hazardous (p)) {
-               DelayedFreeItem item = { p, free_func };
+               DelayedFreeItem item = { p, free_func, free_func_might_lock };
 
-               ++mono_stats.hazardous_pointer_count;
+               ++hazardous_pointer_count;
 
-               delayed_free_push (item);
+               mono_lock_free_array_queue_push (&delayed_free_queue, &item);
        } else {
                free_func (p);
        }
@@ -383,30 +328,38 @@ mono_thread_hazardous_free_or_queue (gpointer p, MonoHazardousFreeFunc free_func
 void
 mono_thread_hazardous_try_free_all (void)
 {
-       while (try_free_delayed_free_item ())
+       while (try_free_delayed_free_item (FALSE))
                ;
 }
 
+void
+mono_thread_hazardous_try_free_some (void)
+{
+       int i;
+       for (i = 0; i < 10; ++i)
+               try_free_delayed_free_item (FALSE);
+}
+
 void
 mono_thread_smr_init (void)
 {
+       int i;
+
        InitializeCriticalSection(&small_id_mutex);
+       mono_counters_register ("Hazardous pointers", MONO_COUNTER_JIT | MONO_COUNTER_INT, &hazardous_pointer_count);
+
+       for (i = 0; i < HAZARD_TABLE_OVERFLOW; ++i) {
+               int small_id = mono_thread_small_id_alloc ();
+               g_assert (small_id == i);
+       }
 }
 
 void
 mono_thread_smr_cleanup (void)
 {
-       DelayedFreeChunk *chunk;
-
        mono_thread_hazardous_try_free_all ();
 
-       chunk = delayed_free_chunk_list;
-       delayed_free_chunk_list = NULL;
-       while (chunk) {
-               DelayedFreeChunk *next = chunk->next;
-               free_delayed_free_chunk (chunk);
-               chunk = next;
-       }
+       mono_lock_free_array_queue_cleanup (&delayed_free_queue);
 
        /*FIXME, can't we release the small id table here?*/
 }