winpthreads: spinlock.c: add fast lock paths

These fast paths are equivalent to those used for stalled mutexes in mutex.c.

Signed-off-by: Kirill Makurin <maiddaisuki@outlook.com>
Signed-off-by: LIU Hao <lh_mouse@126.com>
diff --git a/mingw-w64-libraries/winpthreads/src/spinlock.c b/mingw-w64-libraries/winpthreads/src/spinlock.c
index 2f05164..9867091 100644
--- a/mingw-w64-libraries/winpthreads/src/spinlock.c
+++ b/mingw-w64-libraries/winpthreads/src/spinlock.c
@@ -48,10 +48,41 @@
  */
 
 /**
+ * Spinlock lock state.
+ */
+typedef enum {
+  /**
+   * Spinlock is unlocked.
+   */
+  Unlocked,
+  /**
+   * Spinlock is locked.
+   *
+   * This state inicates that there are no blocked threads waiting for
+   * the spinlock to be released.
+   *
+   * While in this state, if any thread blocks waiting for the spinlock to be
+   * released, the lock state will change to `LockedWithBlocking`.
+   */
+  Locked,
+  /**
+   * Spinlock is locked.
+   *
+   * This state indicates that there can be one or more blocked threads
+   * waiting for the spinlock to be released.
+   */
+  LockedWithBlocking,
+} WinpthreadsSpinlockLockState;
+
+/**
  * Internal structure pointed to by `pthread_spinlock_t` objects.
  */
 typedef struct {
   /**
+   * One of `WinpthreadsSpinlockLockState` values.
+   */
+  LONG State;
+  /**
    * This value is used to indicate that spin lock has no owner.
    */
 #define THREAD_ID_NO_OWNER ((DWORD)-1)
@@ -62,15 +93,8 @@
   /**
    * Auto-reset event.
    *
-   * This event is created in signaled state, which means any thread can
-   * `WaitForSingleObject` on it; only one thread will be released at a time,
-   * after which this event will be automatically put into non-signaled state.
-   *
-   * The released thread owns the lock; it unlocks it by calling `SetEvent` on
-   * this event, which puts this event into signaled state, allowing system
-   * release another thread which waits on it.
-   *
-   * The cycle repeats until this event is destroyed.
+   * When a thread releases the spinlock and `State` is `LockedWithBlocking`,
+   * this event will be signaled in order to release one blocked thread.
    */
   HANDLE Event;
 } WinpthreadsSpinlock;
@@ -170,8 +194,9 @@
     return ENOMEM;
   }
 
+  wSpinlock->State    = Unlocked;
   wSpinlock->ThreadId = THREAD_ID_NO_OWNER;
-  wSpinlock->Event    = CreateEventW (NULL, FALSE, TRUE, NULL);
+  wSpinlock->Event    = CreateEventW (NULL, FALSE, FALSE, NULL);
 
   /**
    * The pthread_spin_init() function shall fail if:
@@ -221,29 +246,17 @@
     return 0;
   }
 
-  switch (_pthread_wait_for_single_object (wSpinlock->Event, 0)) {
-    /**
-     * `wSpinlock->Event` was in signaled state, which means it was unlocked;
-     * we are holding the lock now which prevents other threads from locking it.
-     */
-    case WAIT_OBJECT_0:
-      break;
-    /**
-     * `wSpinlock->Event` was in not-signaled state, which means some thread
-     * holds the lock.
-     *
-     * POSIX:
-     *
-     * If an implementation detects that the value specified by the lock argument
-     * to pthread_spin_destroy() or pthread_spin_init() refers to a locked spin
-     * lock object, or detects that the value specified by the lock argument to
-     * pthread_spin_init() refers to an already initialized spin lock object,
-     * it is recommended that the function should fail and report an [EBUSY] error.
-     */
-    case WAIT_TIMEOUT:
-      return EBUSY;
-    default:
-      return EINVAL;
+  /**
+   * POSIX:
+   *
+   * If an implementation detects that the value specified by the lock argument
+   * to pthread_spin_destroy() or pthread_spin_init() refers to a locked spin
+   * lock object, or detects that the value specified by the lock argument to
+   * pthread_spin_init() refers to an already initialized spin lock object,
+   * it is recommended that the function should fail and report an [EBUSY] error.
+   */
+  if (unlikely (InterlockedCompareExchange (&wSpinlock->State, Locked, Unlocked) != Unlocked)) {
+    return EBUSY;
   }
 
   /**
@@ -279,17 +292,37 @@
     return EDEADLK;
   }
 
-  switch (_pthread_wait_for_single_object (wSpinlock->Event, INFINITE)) {
-    /**
-     * We are holding the lock now and `wSpinlock->Event` was reset to
-     * non-signaled state.
-     */
-    case WAIT_OBJECT_0:
-      break;
-    default:
-      return EINVAL;
+  /**
+   * Try fast path.
+   */
+  if (InterlockedCompareExchange (&wSpinlock->State, Locked, Unlocked) == Unlocked) {
+    goto locked;
   }
 
+  /**
+   * Setting `wSpinlock->State` to `LockedWithBlocking` causes
+   * `pthread_spin_unlock` to signal `wSpinlock->Event`.
+   */
+  LONG oldLockState = InterlockedExchange (&wSpinlock->State, LockedWithBlocking);
+
+  if (likely (oldLockState != Unlocked)) {
+    do {
+      switch (_pthread_wait_for_single_object (wSpinlock->Event, INFINITE)) {
+        /**
+         * `wSpinlock->Event` was signaled.
+         *
+         * There is a small chance that another thread grabs the lock faster
+         * than we do; lock state is updated before wait handle is signaled.
+         */
+        case WAIT_OBJECT_0:
+          break;
+        default:
+          return EINVAL;
+      }
+    } while (unlikely (InterlockedExchange (&wSpinlock->State, LockedWithBlocking) != Unlocked));
+  }
+
+locked:
   wSpinlock->ThreadId = threadId;
 
   return 0;
@@ -306,30 +339,13 @@
   }
 
   /**
-   * Unlike `pthread_spin_lock`, no deadlock can occur even if calling thread
-   * owns the lock.
+   * The pthread_spin_trylock() function shall fail if:
+   *
+   * [EBUSY]
+   *  A thread currently holds the lock.
    */
-  switch (_pthread_wait_for_single_object (wSpinlock->Event, 0)) {
-    /**
-     * `wSpinlock->Event` was in signaled state, which means it was unlocked;
-     * we are holding the lock now and `wSpinlock->Event` was reset to
-     * non-signaled state.
-     */
-    case WAIT_OBJECT_0:
-      break;
-    /**
-     * `wSpinlock->Event` was in non-signaled state, which means some thread
-     * holds the lock.
-     *
-     * The pthread_spin_trylock() function shall fail if:
-     *
-     * [EBUSY]
-     *  A thread currently holds the lock.
-     */
-    case WAIT_TIMEOUT:
-      return EBUSY;
-    default:
-      return EINVAL;
+  if (InterlockedCompareExchange (&wSpinlock->State, Locked, Unlocked) != Unlocked) {
+    return EBUSY;
   }
 
   wSpinlock->ThreadId = GetCurrentThreadId ();
@@ -363,8 +379,14 @@
 
   wSpinlock->ThreadId = THREAD_ID_NO_OWNER;
 
-  if (!SetEvent (wSpinlock->Event)) {
-    return EINVAL;
+  /**
+   * If `wSpinlock->State` is `LockedWithBlocking`, then some other thread is
+   * waiting for `wSpinlock->Event` to become signaled.
+   */
+  if (InterlockedExchange (&wSpinlock->State, Unlocked) == LockedWithBlocking) {
+    if (!SetEvent (wSpinlock->Event)) {
+      return EINVAL;
+    }
   }
 
   return 0;