#include <mono/metadata/threads.h>
#include <mono/io-layer/io-layer.h>
#include <mono/metadata/object-internals.h>
-
-#include <mono/os/gc_wrapper.h>
+#include <mono/metadata/gc-internal.h>
/*#define LOCK_DEBUG(a) do { a; } while (0)*/
#define LOCK_DEBUG(a)
* an object.
*/
+struct _MonoThreadsSync
+{
+ gsize owner; /* thread ID */
+ guint32 nest;
+#ifdef HAVE_MOVING_COLLECTOR
+ gint32 hash_code;
+#endif
+ volatile guint32 entry_count;
+ HANDLE entry_sem;
+ GSList *wait_list;
+ void *data;
+};
+
+typedef struct _MonitorArray MonitorArray;
+
+struct _MonitorArray {
+ MonitorArray *next;
+ int num_monitors;
+ MonoThreadsSync monitors [MONO_ZERO_LEN_ARRAY];
+};
+
+#define mono_monitor_allocator_lock() EnterCriticalSection (&monitor_mutex)
+#define mono_monitor_allocator_unlock() LeaveCriticalSection (&monitor_mutex)
+static CRITICAL_SECTION monitor_mutex;
+static MonoThreadsSync *monitor_freelist;
+static MonitorArray *monitor_allocated;
+static int array_size = 16;
+
+void
+mono_monitor_init (void)
+{
+ InitializeCriticalSection (&monitor_mutex);
+}
+
+void
+mono_monitor_cleanup (void)
+{
+ DeleteCriticalSection (&monitor_mutex);
+}
+/* LOCKING: this is called with monitor_mutex held */
static void
-mon_finalize (void *o, void *unused)
+mon_finalize (MonoThreadsSync *mon)
{
- MonoThreadsSync *mon = o;
-
LOCK_DEBUG (g_message (G_GNUC_PRETTY_FUNCTION ": Finalizing sync %p", mon));
if (mon->entry_sem != NULL) {
CloseHandle (mon->entry_sem);
+ mon->entry_sem = NULL;
}
/* If this isn't empty then something is seriously broken - it
* means a thread is still waiting on the object that owned
* this lock, but the object has been finalized.
*/
g_assert (mon->wait_list == NULL);
+
+ mon->entry_count = 0;
+ /* owner and nest are set in mon_new, no need to zero them out */
+
+ mon->data = monitor_freelist;
+ monitor_freelist = mon;
}
+/* LOCKING: this is called with monitor_mutex held */
static MonoThreadsSync *
-mon_new (guint32 id)
+mon_new (gsize id)
{
MonoThreadsSync *new;
-
-#if HAVE_BOEHM_GC
- new = GC_MALLOC (sizeof (MonoThreadsSync));
- GC_REGISTER_FINALIZER (new, mon_finalize, NULL, NULL, NULL);
-#else
- /* This should be freed when the object that owns it is
- * deleted
- */
- new = g_new0 (MonoThreadsSync, 1);
-#endif
+
+ if (!monitor_freelist) {
+ MonitorArray *marray;
+ int i;
+ /* see if any sync block has been collected */
+ new = NULL;
+ for (marray = monitor_allocated; marray; marray = marray->next) {
+ for (i = 0; i < marray->num_monitors; ++i) {
+ if (marray->monitors [i].data == NULL) {
+ new = &marray->monitors [i];
+ new->data = monitor_freelist;
+ monitor_freelist = new;
+ }
+ }
+ /* small perf tweak to avoid scanning all the blocks */
+ if (new)
+ break;
+ }
+ /* need to allocate a new array of monitors */
+ if (!monitor_freelist) {
+ MonitorArray *last;
+ LOCK_DEBUG (g_message (G_GNUC_PRETTY_FUNCTION ": allocating more monitors: %d", array_size));
+ marray = g_malloc0 (sizeof (MonoArray) + array_size * sizeof (MonoThreadsSync));
+ marray->num_monitors = array_size;
+ array_size *= 2;
+ /* link into the freelist */
+ for (i = 0; i < marray->num_monitors - 1; ++i) {
+ marray->monitors [i].data = &marray->monitors [i + 1];
+ }
+ marray->monitors [i].data = NULL; /* the last one */
+ monitor_freelist = &marray->monitors [0];
+ /* we happend the marray instead of prepending so that
+ * the collecting loop above will need to scan smaller arrays first
+ */
+ if (!monitor_allocated) {
+ monitor_allocated = marray;
+ } else {
+ last = monitor_allocated;
+ while (last->next)
+ last = last->next;
+ last->next = marray;
+ }
+ }
+ }
+
+ new = monitor_freelist;
+ monitor_freelist = new->data;
+
new->owner = id;
new->nest = 1;
return new;
}
+/*
+ * Format of the lock word:
+ * thinhash | fathash | data
+ *
+ * thinhash is the lower bit: if set data is the shifted hashcode of the object.
+ * fathash is another bit: if set the hash code is stored in the MonoThreadsSync
+ * struct pointed to by data
+ * if neither bit is set and data is non-NULL, data is a MonoThreadsSync
+ */
+typedef union {
+ gsize lock_word;
+ MonoThreadsSync *sync;
+} LockWord;
+
+enum {
+ LOCK_WORD_THIN_HASH = 1,
+ LOCK_WORD_FAT_HASH = 1 << 1,
+ LOCK_WORD_BITS_MASK = 0x3,
+ LOCK_WORD_HASH_SHIFT = 2
+};
+
+#define MONO_OBJECT_ALIGNMENT_SHIFT 3
+
+/*
+ * mono_object_hash:
+ * @obj: an object
+ *
+ * Calculate a hash code for @obj that is constant while @obj is alive.
+ */
+int
+mono_object_hash (MonoObject* obj)
+{
+#ifdef HAVE_MOVING_COLLECTOR
+ LockWord lw;
+ unsigned int hash;
+ if (!obj)
+ return 0;
+ lw.sync = obj->synchronisation;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ /*g_print ("fast thin hash %d for obj %p store\n", (unsigned int)lw.lock_word >> LOCK_WORD_HASH_SHIFT, obj);*/
+ return (unsigned int)lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+ }
+ if (lw.lock_word & LOCK_WORD_FAT_HASH) {
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ /*g_print ("fast fat hash %d for obj %p store\n", lw.sync->hash_code, obj);*/
+ return lw.sync->hash_code;
+ }
+ /*
+ * while we are inside this function, the GC will keep this object pinned,
+ * since we are in the unmanaged stack. Thanks to this and to the hash
+ * function that depends only on the address, we can ignore the races if
+ * another thread computes the hash at the same time, because it'll end up
+ * with the same value.
+ */
+ hash = (GPOINTER_TO_UINT (obj) >> MONO_OBJECT_ALIGNMENT_SHIFT) * 2654435761u;
+ /* clear the top bits as they can be discarded */
+ hash &= ~(LOCK_WORD_BITS_MASK << 30);
+ /* no hash flags were set, so it must be a MonoThreadsSync pointer if not NULL */
+ if (lw.sync) {
+ lw.sync->hash_code = hash;
+ /*g_print ("storing hash code %d for obj %p in sync %p\n", hash, obj, lw.sync);*/
+ lw.lock_word |= LOCK_WORD_FAT_HASH;
+ /* this is safe since we don't deflate locks */
+ obj->synchronisation = lw.sync;
+ } else {
+ /*g_print ("storing thin hash code %d for obj %p\n", hash, obj);*/
+ lw.lock_word = LOCK_WORD_THIN_HASH | (hash << LOCK_WORD_HASH_SHIFT);
+ if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, NULL) == NULL)
+ return hash;
+ /*g_print ("failed store\n");*/
+ /* someone set the hash flag or someone inflated the object */
+ lw.sync = obj->synchronisation;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH)
+ return hash;
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ lw.sync->hash_code = hash;
+ lw.lock_word |= LOCK_WORD_FAT_HASH;
+ /* this is safe since we don't deflate locks */
+ obj->synchronisation = lw.sync;
+ }
+ return hash;
+#else
+/*
+ * Wang's address-based hash function:
+ * http://www.concentric.net/~Ttwang/tech/addrhash.htm
+ */
+ return (GPOINTER_TO_UINT (obj) >> MONO_OBJECT_ALIGNMENT_SHIFT) * 2654435761u;
+#endif
+}
+
/* If allow_interruption==TRUE, the method will be interrumped if abort or suspend
* is requested. In this case it returns -1.
*/
mono_monitor_try_enter_internal (MonoObject *obj, guint32 ms, gboolean allow_interruption)
{
MonoThreadsSync *mon;
- guint32 id = GetCurrentThreadId ();
+ gsize id = GetCurrentThreadId ();
HANDLE sem;
guint32 then = 0, now, delta;
guint32 waitms;
/* If the object has never been locked... */
if (mon == NULL) {
+ mono_monitor_allocator_lock ();
mon = mon_new (id);
if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, mon, NULL) == NULL) {
+ mono_gc_weak_link_add (&mon->data, obj);
+ mono_monitor_allocator_unlock ();
/* Successfully locked */
return 1;
} else {
- /* Another thread got in first, so try again.
- * GC will take care of the monitor record
- */
-#ifndef HAVE_BOEHM_GC
- mon_finalize (mon, NULL);
+#ifdef HAVE_MOVING_COLLECTOR
+ LockWord lw;
+ lw.sync = obj->synchronisation;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ MonoThreadsSync *oldlw = lw.sync;
+ /* move the already calculated hash */
+ mon->hash_code = lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+ lw.sync = mon;
+ lw.lock_word |= LOCK_WORD_FAT_HASH;
+ if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, oldlw) == oldlw) {
+ mono_gc_weak_link_add (&mon->data, obj);
+ mono_monitor_allocator_unlock ();
+ /* Successfully locked */
+ return 1;
+ } else {
+ mon_finalize (mon);
+ mono_monitor_allocator_unlock ();
+ goto retry;
+ }
+ } else if (lw.lock_word & LOCK_WORD_FAT_HASH) {
+ mon_finalize (mon);
+ mono_monitor_allocator_unlock ();
+ /* get the old lock without the fat hash bit */
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ } else {
+ mon_finalize (mon);
+ mono_monitor_allocator_unlock ();
+ mon = obj->synchronisation;
+ }
+#else
+ mon_finalize (mon);
+ mono_monitor_allocator_unlock ();
+ mon = obj->synchronisation;
#endif
- goto retry;
}
+ } else {
+#ifdef HAVE_MOVING_COLLECTOR
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ MonoThreadsSync *oldlw = lw.sync;
+ mono_monitor_allocator_lock ();
+ mon = mon_new (id);
+ /* move the already calculated hash */
+ mon->hash_code = lw.lock_word >> LOCK_WORD_HASH_SHIFT;
+ lw.sync = mon;
+ lw.lock_word |= LOCK_WORD_FAT_HASH;
+ if (InterlockedCompareExchangePointer ((gpointer*)&obj->synchronisation, lw.sync, oldlw) == oldlw) {
+ mono_gc_weak_link_add (&mon->data, obj);
+ mono_monitor_allocator_unlock ();
+ /* Successfully locked */
+ return 1;
+ } else {
+ mon_finalize (mon);
+ mono_monitor_allocator_unlock ();
+ goto retry;
+ }
+ }
+#endif
+ }
+
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
}
+#endif
/* If the object is currently locked by this thread... */
if (mon->owner == id) {
* should have been left at 1 by the previous unlock
* operation
*/
- if (InterlockedCompareExchange (&mon->owner, id, 0) == 0) {
+ if (InterlockedCompareExchangePointer ((gpointer *)&mon->owner, (gpointer)id, 0) == 0) {
/* Success */
g_assert (mon->nest == 1);
return 1;
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH)
+ return;
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
- mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+ /* No one ever used Enter. Just ignore the Exit request as MS does */
return;
}
if (mon->owner != GetCurrentThreadId ()) {
": Testing if %p is owned by thread %d", obj, GetCurrentThreadId()));
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH)
+ return FALSE;
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
return FALSE;
}
": (%d) Testing if %p is owned by any thread", GetCurrentThreadId (), obj));
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH)
+ return FALSE;
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
return FALSE;
}
GetCurrentThreadId (), obj));
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+ return;
+ }
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
return;
GetCurrentThreadId (), obj));
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+ return;
+ }
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
return;
GetCurrentThreadId (), obj, ms));
mon = obj->synchronisation;
+#ifdef HAVE_MOVING_COLLECTOR
+ {
+ LockWord lw;
+ lw.sync = mon;
+ if (lw.lock_word & LOCK_WORD_THIN_HASH) {
+ mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
+ return FALSE;
+ }
+ lw.lock_word &= ~LOCK_WORD_BITS_MASK;
+ mon = lw.sync;
+ }
+#endif
if (mon == NULL) {
mono_raise_exception (mono_get_exception_synchronization_lock ("Not locked"));
return FALSE;