+/*
+ * This is a reader/writer spin lock of sorts used to protect log buffers.
+ * When a thread modifies its own log buffer, it increments the reader
+ * count. When a thread wants to access log buffers of other threads, it
+ * takes the exclusive lock.
+ *
+ * `buffer_lock_state` holds the reader count in its lower 16 bits, and
+ * the small ID of the thread currently holding the exclusive (writer)
+ * lock in its upper 16 bits. Both can be zero. It's important that the
+ * whole lock state is a single word that can be read/written atomically
+ * to avoid race conditions where there could end up being readers while
+ * the writer lock is held.
+ *
+ * The lock is writer-biased. When a thread wants to take the exclusive
+ * lock, it increments `buffer_lock_exclusive_intent` which will make new
+ * readers spin until it's back to zero, then takes the exclusive lock
+ * once the reader count has reached zero. After releasing the exclusive
+ * lock, it decrements `buffer_lock_exclusive_intent`, which, when it
+ * reaches zero again, allows readers to increment the reader count.
+ *
+ * The writer bias is necessary because we take the exclusive lock in
+ * `gc_event ()` during STW. If the writer bias was not there, and a
+ * program had a large number of threads, STW-induced pauses could be
+ * significantly longer than they have to be. Also, we emit periodic
+ * sync points from the helper thread, which requires taking the
+ * exclusive lock, and we need those to arrive with a reasonably
+ * consistent frequency so that readers don't have to queue up too many
+ * events between sync points.
+ */
+static volatile gint32 buffer_lock_state;
+static volatile gint32 buffer_lock_exclusive_intent;
+
+// Can be used recursively.
+static void
+buffer_lock (void)
+{
+ /*
+ * If the thread holding the exclusive lock tries to modify the
+ * reader count, just make it a no-op. This way, we also avoid
+ * invoking the GC safe point macros below, which could break if
+ * done from a thread that is currently the initiator of STW.
+ *
+ * In other words, we rely on the fact that the GC thread takes
+ * the exclusive lock in the gc_event () callback when the world
+ * is about to stop.
+ */
+ if (InterlockedRead (&buffer_lock_state) != get_thread ()->small_id << 16) {
+ MONO_ENTER_GC_SAFE;
+
+ gint32 old, new_;
+
+ do {
+ restart:
+ // Hold off if a thread wants to take the exclusive lock.
+ while (InterlockedRead (&buffer_lock_exclusive_intent))
+ mono_thread_info_yield ();
+
+ old = InterlockedRead (&buffer_lock_state);
+
+ // Is a thread holding the exclusive lock?
+ if (old >> 16) {
+ mono_thread_info_yield ();
+ goto restart;
+ }
+
+ new_ = old + 1;
+ } while (InterlockedCompareExchange (&buffer_lock_state, new_, old) != old);
+
+ MONO_EXIT_GC_SAFE;
+ }
+
+ mono_memory_barrier ();
+}
+
+static void
+buffer_unlock (void)
+{
+ mono_memory_barrier ();
+
+ gint32 state = InterlockedRead (&buffer_lock_state);
+
+ // See the comment in buffer_lock ().
+ if (state == PROF_TLS_GET ()->small_id << 16)
+ return;
+
+ g_assert (state && "Why are we decrementing a zero reader count?");
+ g_assert (!(state >> 16) && "Why is the exclusive lock held?");
+
+ InterlockedDecrement (&buffer_lock_state);
+}
+
+// Cannot be used recursively.
+static void
+buffer_lock_excl (void)
+{
+ gint32 new_ = get_thread ()->small_id << 16;
+
+ g_assert (InterlockedRead (&buffer_lock_state) != new_ && "Why are we taking the exclusive lock twice?");
+
+ InterlockedIncrement (&buffer_lock_exclusive_intent);
+
+ MONO_ENTER_GC_SAFE;
+
+ while (InterlockedCompareExchange (&buffer_lock_state, new_, 0))
+ mono_thread_info_yield ();
+
+ MONO_EXIT_GC_SAFE;
+
+ mono_memory_barrier ();
+}
+
+static void
+buffer_unlock_excl (void)
+{
+ mono_memory_barrier ();
+
+ gint32 state = InterlockedRead (&buffer_lock_state);
+ gint32 excl = state >> 16;
+
+ g_assert (excl && "Why is the exclusive lock not held?");
+ g_assert (excl == PROF_TLS_GET ()->small_id && "Why does another thread hold the exclusive lock?");
+ g_assert (!(state & 0xFFFF) && "Why are there readers when the exclusive lock is held?");
+
+ InterlockedWrite (&buffer_lock_state, 0);
+ InterlockedDecrement (&buffer_lock_exclusive_intent);
+}
+