The code of module 'asyncsafe-spin' consists of a multithread-safe spin lock
implementation (that works fine even on native Windows), with an add-on
intented to make it async signal safe (this part does not work reliably on
native Windows).

This suggests that it makes sense to rebase 'asyncsafe-spin' on top of a
module 'spin', that implements purely the multithread-safe spin locks.


2025-05-14  Bruno Haible  <br...@clisp.org>

        asyncsafe-spin: Rely on module 'spin'.
        * lib/asyncsafe-spin.h: Include glthread/spin.h.
        (asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using
        gl_spinlock_t, gl_spinlock_initializer.
        * lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions.
        * modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4.
        (Depends-on): Add spin. Remove bool, windows-spin, sparcv8+.
        (configure.ac): Remove tests.

2025-05-14  Bruno Haible  <br...@clisp.org>

        spin: Add tests.
        * tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c.
        * tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c.
        * modules/spin-tests: New file.

        spin: New module.
        * lib/glthread/spin.h: New file, based on lib/glthread/lock.h and
        lib/asyncsafe-spin.h.
        * lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c.
        * modules/spin: New file.
        * doc/multithread.texi (Choosing a multithreading API): Spin locks no
        longer require POSIX threads.
        (Gnulib multithreading): Mention the 'spin' module.

>From a258598ec7d20ad6a853ee2c5e811a35b506a1f2 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 14 May 2025 12:51:46 +0200
Subject: [PATCH 1/3] spin: New module.

* lib/glthread/spin.h: New file, based on lib/glthread/lock.h and
lib/asyncsafe-spin.h.
* lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c.
* modules/spin: New file.
* doc/multithread.texi (Choosing a multithreading API): Spin locks no
longer require POSIX threads.
(Gnulib multithreading): Mention the 'spin' module.
---
 ChangeLog            |  11 ++
 doc/multithread.texi |   6 +-
 lib/glthread/spin.c  | 298 +++++++++++++++++++++++++++++++++++++++++++
 lib/glthread/spin.h  |  94 ++++++++++++++
 modules/bind-tests   |   1 +
 modules/spin         |  30 +++++
 6 files changed, 439 insertions(+), 1 deletion(-)
 create mode 100644 lib/glthread/spin.c
 create mode 100644 lib/glthread/spin.h
 create mode 100644 modules/spin

diff --git a/ChangeLog b/ChangeLog
index 85028d3684..6c37f8b1a4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2025-05-14  Bruno Haible  <br...@clisp.org>
+
+	spin: New module.
+	* lib/glthread/spin.h: New file, based on lib/glthread/lock.h and
+	lib/asyncsafe-spin.h.
+	* lib/glthread/spin.c: New file, based on lib/asyncsafe-spin.c.
+	* modules/spin: New file.
+	* doc/multithread.texi (Choosing a multithreading API): Spin locks no
+	longer require POSIX threads.
+	(Gnulib multithreading): Mention the 'spin' module.
+
 2025-05-12  Bruno Haible  <br...@clisp.org>
 
 	file-has-acl: Fix compilation error on Solaris (regression 2025-05-09).
diff --git a/doc/multithread.texi b/doc/multithread.texi
index b24c35a207..1316404bef 100644
--- a/doc/multithread.texi
+++ b/doc/multithread.texi
@@ -107,7 +107,7 @@
 Here are guidelines for determining which multithreading API is best for
 your code.
 
-In programs that use advanced POSIX APIs, such as spin locks,
+In programs that use advanced POSIX APIs, such as
 detached threads (@code{pthread_detach}),
 signal blocking (@code{pthread_sigmask}),
 priorities (@code{pthread_setschedparam}),
@@ -204,6 +204,8 @@
 @code{<glthread/tls.h>}
 @item
 @code{<glthread/yield.h>}
+@item
+@code{<glthread/spin.h>}
 @end itemize
 
 To make use of Gnulib multithreading, use the following Gnulib modules:
@@ -213,6 +215,7 @@
 @mindex cond
 @mindex tls
 @mindex yield
+@mindex spin
 @multitable @columnfractions .85 .15
 @headitem Purpose @tab Module
 @item For thread creation and management:@tie{} @tab @code{thread}
@@ -222,6 +225,7 @@
 @item For ``condition variables'' (wait queues):@tie{} @tab @code{cond}
 @item For thread-local storage:@tie{} @tab @code{tls}
 @item For relinquishing control:@tie{} @tab @code{yield}
+@item For spin locks:@tie{} @tab @code{spin}
 @end multitable
 
 The Gnulib multithreading supports a configure option
diff --git a/lib/glthread/spin.c b/lib/glthread/spin.c
new file mode 100644
index 0000000000..c8dd483b64
--- /dev/null
+++ b/lib/glthread/spin.c
@@ -0,0 +1,298 @@
+/* Spin locks in multithreaded situations.
+   Copyright (C) 2020-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "glthread/spin.h"
+
+#include <errno.h>
+#if defined _AIX
+# include <sys/atomic_op.h>
+#endif
+#if 0x590 <= __SUNPRO_C && __STDC__
+# define asm __asm
+#endif
+
+#if defined _WIN32 && !defined __CYGWIN__
+/* Use Windows threads.  */
+
+/* All definitions are inline in glthread/spin.h.  */
+
+#else
+
+/* We don't use semaphores (although sem_post() is allowed in signal handlers),
+   because it would require to link with -lrt on HP-UX 11, OSF/1, Solaris 10,
+   and also because on macOS only named semaphores work.
+
+   We don't use the C11 <stdatomic.h> (available in GCC >= 4.9) because it would
+   require to link with -latomic.  */
+
+# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) \
+      || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) \
+     && !defined __ibmxl__
+/* Use GCC built-ins (available in GCC >= 4.7 and clang >= 3.1) that operate on
+   the first byte of the lock.
+   Documentation:
+   <https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html>
+ */
+
+#  if 1
+/* An implementation that verifies the unlocks.  */
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  __atomic_store_n (lock, 0, __ATOMIC_SEQ_CST);
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  /* Wait until *lock becomes 0, then replace it with 1.  */
+  gl_spinlock_t zero;
+  while (!(zero = 0,
+           __atomic_compare_exchange_n (lock, &zero, 1, false,
+                                        __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)))
+    ;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  /* If *lock is 1, then replace it with 0.  */
+  gl_spinlock_t one = 1;
+  if (!__atomic_compare_exchange_n (lock, &one, 0, false,
+                                    __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
+    return EINVAL;
+  return 0;
+}
+
+#  else
+/* An implementation that is a little bit more optimized, but does not verify
+   the unlocks.  */
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  __atomic_clear (lock, __ATOMIC_SEQ_CST);
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST))
+    ;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  __atomic_clear (lock, __ATOMIC_SEQ_CST);
+  return 0;
+}
+
+#  endif
+
+# elif (((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) \
+         || __clang_major__ >= 3) \
+        && HAVE_ATOMIC_COMPARE_AND_SWAP_GCC41)
+/* Use GCC built-ins (available on many platforms with GCC >= 4.1 or
+   clang >= 3.0).
+   Documentation:
+   <https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html>  */
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+  __sync_synchronize ();
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  /* Wait until *lock becomes 0, then replace it with 1.  */
+  while (__sync_val_compare_and_swap (lock, 0, 1) != 0)
+    ;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  /* If *lock is 1, then replace it with 0.  */
+  if (__sync_val_compare_and_swap (lock, 1, 0) != 1)
+    return EINVAL;
+  return 0;
+}
+
+# elif defined _AIX
+/* AIX */
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  atomic_p vp = (int *) lock;
+  _clear_lock (vp, 0);
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  atomic_p vp = (int *) lock;
+  while (_check_lock (vp, 0, 1))
+    ;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  atomic_p vp = (int *) lock;
+  if (_check_lock (vp, 1, 0))
+    return EINVAL;
+  return 0;
+}
+
+# elif ((defined __GNUC__ || defined __clang__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)) || (defined __TINYC__ && (defined __i386 || defined __x86_64__))
+/* For older versions of GCC or clang, use inline assembly.
+   GCC, clang, and the Oracle Studio C 12 compiler understand GCC's extended
+   asm syntax, but the plain Oracle Studio C 11 compiler understands only
+   simple asm.  */
+/* An implementation that verifies the unlocks.  */
+
+static void
+memory_barrier (void)
+{
+#  if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
+#   if defined __i386 || defined __x86_64__
+#    if defined __TINYC__ && defined __i386
+  /* Cannot use the SSE instruction "mfence" with this compiler.  */
+  asm volatile ("lock orl $0,(%esp)");
+#    else
+  asm volatile ("mfence");
+#    endif
+#   endif
+#   if defined __sparc
+  asm volatile ("membar 2");
+#   endif
+#  else
+#   if defined __i386 || defined __x86_64__
+  asm ("mfence");
+#   endif
+#   if defined __sparc
+  asm ("membar 2");
+#   endif
+#  endif
+}
+
+/* Store NEWVAL in *VP if the old value *VP is == CMP.
+   Return the old value.  */
+static unsigned int
+atomic_compare_and_swap (volatile unsigned int *vp, unsigned int cmp,
+                         unsigned int newval)
+{
+#  if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
+  unsigned int oldval;
+#   if defined __i386 || defined __x86_64__
+  asm volatile (" lock\n cmpxchgl %3,(%1)"
+                : "=a" (oldval) : "r" (vp), "a" (cmp), "r" (newval) : "memory");
+#   endif
+#   if defined __sparc
+  asm volatile (" cas [%1],%2,%3\n"
+                " mov %3,%0"
+                : "=r" (oldval) : "r" (vp), "r" (cmp), "r" (newval) : "memory");
+#   endif
+  return oldval;
+#  else /* __SUNPRO_C */
+#   if defined __x86_64__
+  asm (" movl %esi,%eax\n"
+       " lock\n cmpxchgl %edx,(%rdi)");
+#   elif defined __i386
+  asm (" movl 16(%ebp),%ecx\n"
+       " movl 12(%ebp),%eax\n"
+       " movl 8(%ebp),%edx\n"
+       " lock\n cmpxchgl %ecx,(%edx)");
+#   endif
+#   if defined __sparc
+  asm (" cas [%i0],%i1,%i2\n"
+       " mov %i2,%i0");
+#   endif
+#  endif
+}
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+  memory_barrier ();
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  while (atomic_compare_and_swap (vp, 0, 1) != 0)
+    ;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  if (atomic_compare_and_swap (vp, 1, 0) != 1)
+    return EINVAL;
+  return 0;
+}
+
+# else
+/* Fallback code.  It has some race conditions.  */
+
+void
+glthread_spinlock_init (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+}
+
+void
+glthread_spinlock_lock (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  while (*vp)
+    ;
+  *vp = 1;
+}
+
+int
+glthread_spinlock_unlock (gl_spinlock_t *lock)
+{
+  volatile unsigned int *vp = lock;
+  *vp = 0;
+  return 0;
+}
+
+# endif
+
+void
+glthread_spinlock_destroy (gl_spinlock_t *lock)
+{
+}
+
+#endif
diff --git a/lib/glthread/spin.h b/lib/glthread/spin.h
new file mode 100644
index 0000000000..8a2405f940
--- /dev/null
+++ b/lib/glthread/spin.h
@@ -0,0 +1,94 @@
+/* Spin locks in multithreaded situations.
+   Copyright (C) 2005-2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+/* This file contains short-duration locking primitives for use with a given
+   thread library.
+
+   Spin locks:
+     Type:                gl_spinlock_t
+     Declaration:         gl_spinlock_define(extern, name)
+     Initializer:         gl_spinlock_define_initialized(, name)
+     Initialization:      gl_spinlock_init (name);
+     Taking the lock:     gl_spinlock_lock (name);
+     Releasing the lock:  gl_spinlock_unlock (name);
+     De-initialization:   gl_spinlock_destroy (name);
+   Equivalent functions with control of error handling:
+     Initialization:      glthread_spinlock_init (&name);
+     Taking the lock:     glthread_spinlock_lock (&name);
+     Releasing the lock:  err = glthread_spinlock_unlock (&name);
+     De-initialization:   glthread_spinlock_destroy (&name);
+*/
+
+
+#ifndef _SPINLOCK_H
+#define _SPINLOCK_H
+
+#if defined _WIN32 && !defined __CYGWIN__
+# include "windows-spin.h"
+typedef glwthread_spinlock_t gl_spinlock_t;
+# define gl_spinlock_initializer GLWTHREAD_SPIN_INIT
+#else
+typedef unsigned int gl_spinlock_t;
+# define gl_spinlock_initializer 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+
+#define gl_spinlock_define(STORAGECLASS, NAME) \
+  STORAGECLASS gl_spinlock_t NAME;
+#define gl_spinlock_define_initialized(STORAGECLASS, NAME) \
+  STORAGECLASS gl_spinlock_t NAME = gl_spinlock_initializer;
+#define gl_spinlock_init(NAME) \
+  glthread_spinlock_init (&NAME)
+#define gl_spinlock_lock(NAME) \
+  glthread_spinlock_lock (&NAME)
+#define gl_spinlock_unlock(NAME) \
+   do                                        \
+     {                                       \
+       if (glthread_spinlock_unlock (&NAME)) \
+         abort ();                           \
+     }                                       \
+    while (0)
+#define gl_spinlock_destroy(NAME) \
+  glthread_spinlock_destroy (&NAME)
+
+#if defined _WIN32 && !defined __CYGWIN__
+# define glthread_spinlock_init(lock) \
+    glwthread_spin_init (lock)
+# define glthread_spinlock_lock(lock) \
+    ((void) glwthread_spin_lock (lock))
+# define glthread_spinlock_unlock(lock) \
+    glwthread_spin_unlock (lock)
+# define glthread_spinlock_destroy(lock) \
+    ((void) glwthread_spin_destroy (lock))
+#else
+extern void glthread_spinlock_init (gl_spinlock_t *lock);
+extern void glthread_spinlock_lock (gl_spinlock_t *lock);
+extern int glthread_spinlock_unlock (gl_spinlock_t *lock);
+extern void glthread_spinlock_destroy (gl_spinlock_t *lock);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SPINLOCK_H */
diff --git a/modules/bind-tests b/modules/bind-tests
index 3d2971e55c..f7b5a4782a 100644
--- a/modules/bind-tests
+++ b/modules/bind-tests
@@ -14,3 +14,4 @@ Makefile.am:
 TESTS += test-bind
 check_PROGRAMS += test-bind
 test_bind_LDADD = $(LDADD) @LIBSOCKET@ $(INET_PTON_LIB)
+test_bind_LDFLAGS = $(LDFLAGS)  @LIBSOCKET@ $(INET_PTON_LIB)
diff --git a/modules/spin b/modules/spin
new file mode 100644
index 0000000000..756c7b5e98
--- /dev/null
+++ b/modules/spin
@@ -0,0 +1,30 @@
+Description:
+Spin locks in multithreaded situations.
+
+Files:
+lib/glthread/spin.h
+lib/glthread/spin.c
+m4/atomic-cas.m4
+
+Depends-on:
+windows-spin
+sparcv8+
+
+configure.ac:
+AC_REQUIRE([gl_ATOMIC_COMPARE_AND_SWAP])
+gl_CONDITIONAL([GL_COND_OBJ_SPIN],
+               [case "$host_os" in mingw* | windows*) false;; *) true;; esac])
+
+Makefile.am:
+if GL_COND_OBJ_SPIN
+lib_SOURCES += glthread/spin.c
+endif
+
+Include:
+"glthread/spin.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.43.0

>From 1bf1bea69bb0c590f01d1e312cef8fd71c5dc5e4 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 14 May 2025 13:57:28 +0200
Subject: [PATCH 2/3] spin: Add tests.

* tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c.
* tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c.
* modules/spin-tests: New file.
---
 ChangeLog          |   5 +
 modules/spin-tests |  21 ++++
 tests/test-spin1.c |  52 ++++++++++
 tests/test-spin2.c | 240 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 318 insertions(+)
 create mode 100644 modules/spin-tests
 create mode 100644 tests/test-spin1.c
 create mode 100644 tests/test-spin2.c

diff --git a/ChangeLog b/ChangeLog
index 6c37f8b1a4..b903bacf5e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2025-05-14  Bruno Haible  <br...@clisp.org>
 
+	spin: Add tests.
+	* tests/test-spin1.c: New file, based on tests/test-asyncsafe-spin1.c.
+	* tests/test-spin2.c: New file, based on tests/test-asyncsafe-spin2.c.
+	* modules/spin-tests: New file.
+
 	spin: New module.
 	* lib/glthread/spin.h: New file, based on lib/glthread/lock.h and
 	lib/asyncsafe-spin.h.
diff --git a/modules/spin-tests b/modules/spin-tests
new file mode 100644
index 0000000000..56a29ee10d
--- /dev/null
+++ b/modules/spin-tests
@@ -0,0 +1,21 @@
+Files:
+tests/test-spin1.c
+tests/test-spin2.c
+tests/atomic-int-gnulib.h
+m4/semaphore.m4
+
+Depends-on:
+thread
+lock
+yield
+random
+
+configure.ac:
+AC_CHECK_HEADERS_ONCE([semaphore.h])
+AC_CHECK_DECLS_ONCE([alarm])
+AC_REQUIRE([gl_SEMAPHORE])
+
+Makefile.am:
+TESTS += test-spin1 test-spin2
+check_PROGRAMS += test-spin1 test-spin2
+test_spin2_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@ @LIB_SEMAPHORE@
diff --git a/tests/test-spin1.c b/tests/test-spin1.c
new file mode 100644
index 0000000000..5b82925905
--- /dev/null
+++ b/tests/test-spin1.c
@@ -0,0 +1,52 @@
+/* Test of spin locks in multithreaded situations.
+   Copyright (C) 2005, 2008-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "glthread/spin.h"
+
+gl_spinlock_define_initialized (, global_spin_lock)
+
+int
+main (void)
+{
+  /* Check a spin-lock initialized through the constant initializer.  */
+  {
+    gl_spinlock_lock (global_spin_lock);
+    gl_spinlock_unlock (global_spin_lock);
+  }
+
+  /* Check a spin-lock initialized through gl_spinlock_init.  */
+  {
+    gl_spinlock_define (, local_spin_lock)
+    int i;
+
+    gl_spinlock_init (local_spin_lock);
+
+    for (i = 0; i < 10; i++)
+      {
+        gl_spinlock_lock (local_spin_lock);
+        gl_spinlock_unlock (local_spin_lock);
+      }
+
+    gl_spinlock_destroy (local_spin_lock);
+  }
+
+  return 0;
+}
diff --git a/tests/test-spin2.c b/tests/test-spin2.c
new file mode 100644
index 0000000000..b3baf3c325
--- /dev/null
+++ b/tests/test-spin2.c
@@ -0,0 +1,240 @@
+/* Test of spin locks in multithreaded situations.
+   Copyright (C) 2005, 2008-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2005.  */
+
+#include <config.h>
+
+#if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS
+
+/* Whether to enable locking.
+   Uncomment this to get a test program without locking, to verify that
+   it crashes.  */
+#define ENABLE_LOCKING 1
+
+/* Whether to help the scheduler through explicit yield().
+   Uncomment this to see if the operating system has a fair scheduler.  */
+#define EXPLICIT_YIELD 1
+
+/* Whether to print debugging messages.  */
+#define ENABLE_DEBUGGING 0
+
+/* Number of simultaneous threads.  */
+#define THREAD_COUNT 10
+
+/* Number of operations performed in each thread.  */
+#if !(defined _WIN32 && ! defined __CYGWIN__) && HAVE_PTHREAD_H && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || __clang_major__ >= 3) && !defined __ibmxl__
+
+/* The GCC built-ins are known to work fine.  */
+# define REPEAT_COUNT 5000
+#else
+/* This is quite high, because with a smaller count, say 50000, we often get
+   an "OK" result even with the racy implementation that we pick on Fedora 13
+   Linux/x86_64 (gcc 4.4).  */
+# define REPEAT_COUNT 100000
+#endif
+
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "glthread/spin.h"
+#if !ENABLE_LOCKING
+# undef gl_spinlock_init
+# define gl_spinlock_init(lock) (void)(lock)
+# undef gl_spinlock_lock
+# define gl_spinlock_lock(lock) (void)(lock)
+# undef gl_spinlock_unlock
+# define gl_spinlock_unlock(lock) (void)(lock)
+# undef gl_spinlock_destroy
+# define gl_spinlock_destroy(lock) (void)(lock)
+#endif
+
+#include "glthread/lock.h"
+#include "glthread/thread.h"
+#include "glthread/yield.h"
+
+#if HAVE_DECL_ALARM
+# include <signal.h>
+# include <unistd.h>
+#endif
+
+#include "atomic-int-gnulib.h"
+
+#if ENABLE_DEBUGGING
+# define dbgprintf printf
+#else
+# define dbgprintf if (0) printf
+#endif
+
+#if EXPLICIT_YIELD
+# define yield() gl_thread_yield ()
+#else
+# define yield()
+#endif
+
+#define ACCOUNT_COUNT 4
+
+static int account[ACCOUNT_COUNT];
+
+static int
+random_account (void)
+{
+  return ((unsigned long) random () >> 3) % ACCOUNT_COUNT;
+}
+
+static void
+check_accounts (void)
+{
+  int i, sum;
+
+  sum = 0;
+  for (i = 0; i < ACCOUNT_COUNT; i++)
+    sum += account[i];
+  if (sum != ACCOUNT_COUNT * 1000)
+    abort ();
+}
+
+
+/* ------------------- Test use like normal locks ------------------- */
+
+/* Test normal locks by having several bank accounts and several threads
+   which shuffle around money between the accounts and another thread
+   checking that all the money is still there.  */
+
+gl_spinlock_define (static, my_lock)
+
+static void *
+lock_mutator_thread (void *arg)
+{
+  int repeat;
+
+  for (repeat = REPEAT_COUNT; repeat > 0; repeat--)
+    {
+      int i1, i2, value;
+
+      dbgprintf ("Mutator %p before lock\n", gl_thread_self_pointer ());
+      gl_spinlock_lock (my_lock);
+      dbgprintf ("Mutator %p after  lock\n", gl_thread_self_pointer ());
+
+      i1 = random_account ();
+      i2 = random_account ();
+      value = ((unsigned long) random () >> 3) % 10;
+      account[i1] += value;
+      account[i2] -= value;
+
+      dbgprintf ("Mutator %p before unlock\n", gl_thread_self_pointer ());
+      gl_spinlock_unlock (my_lock);
+      dbgprintf ("Mutator %p after  unlock\n", gl_thread_self_pointer ());
+
+      dbgprintf ("Mutator %p before check lock\n", gl_thread_self_pointer ());
+      gl_spinlock_lock (my_lock);
+      check_accounts ();
+      gl_spinlock_unlock (my_lock);
+      dbgprintf ("Mutator %p after  check unlock\n", gl_thread_self_pointer ());
+
+      yield ();
+    }
+
+  dbgprintf ("Mutator %p dying.\n", gl_thread_self_pointer ());
+  return NULL;
+}
+
+static struct atomic_int lock_checker_done;
+
+static void *
+lock_checker_thread (void *arg)
+{
+  while (get_atomic_int_value (&lock_checker_done) == 0)
+    {
+      dbgprintf ("Checker %p before check lock\n", gl_thread_self_pointer ());
+      gl_spinlock_lock (my_lock);
+      check_accounts ();
+      gl_spinlock_unlock (my_lock);
+      dbgprintf ("Checker %p after  check unlock\n", gl_thread_self_pointer ());
+
+      yield ();
+    }
+
+  dbgprintf ("Checker %p dying.\n", gl_thread_self_pointer ());
+  return NULL;
+}
+
+static void
+test_asyncsafe_spin (void)
+{
+  int i;
+  gl_thread_t checkerthread;
+  gl_thread_t threads[THREAD_COUNT];
+
+  /* Initialization.  */
+  for (i = 0; i < ACCOUNT_COUNT; i++)
+    account[i] = 1000;
+  init_atomic_int (&lock_checker_done);
+  set_atomic_int_value (&lock_checker_done, 0);
+
+  /* Spawn the threads.  */
+  checkerthread = gl_thread_create (lock_checker_thread, NULL);
+  for (i = 0; i < THREAD_COUNT; i++)
+    threads[i] = gl_thread_create (lock_mutator_thread, NULL);
+
+  /* Wait for the threads to terminate.  */
+  for (i = 0; i < THREAD_COUNT; i++)
+    gl_thread_join (threads[i], NULL);
+  set_atomic_int_value (&lock_checker_done, 1);
+  gl_thread_join (checkerthread, NULL);
+  check_accounts ();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+int
+main ()
+{
+#if HAVE_DECL_ALARM
+  /* Declare failure if test takes too long, by using default abort
+     caused by SIGALRM.  */
+  int alarm_value = 600;
+  signal (SIGALRM, SIG_DFL);
+  alarm (alarm_value);
+#endif
+
+  gl_spinlock_init (my_lock);
+
+  printf ("Starting test_asyncsafe_spin ..."); fflush (stdout);
+  test_asyncsafe_spin ();
+  printf (" OK\n"); fflush (stdout);
+
+  return 0;
+}
+
+#else
+
+/* No multithreading available.  */
+
+#include <stdio.h>
+
+int
+main ()
+{
+  fputs ("Skipping test: multithreading not enabled\n", stderr);
+  return 77;
+}
+
+#endif
-- 
2.43.0

>From aba715840c0db7ad2eac29c6be1cb621d8bd14ec Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 14 May 2025 16:27:30 +0200
Subject: [PATCH 3/3] asyncsafe-spin: Rely on module 'spin'.

* lib/asyncsafe-spin.h: Include glthread/spin.h.
(asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using
gl_spinlock_t, gl_spinlock_initializer.
* lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions.
* modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4.
(Depends-on): Add spin. Remove bool, windows-spin, sparcv8+.
(configure.ac): Remove tests.
---
 ChangeLog              |  11 ++
 lib/asyncsafe-spin.c   | 319 ++---------------------------------------
 lib/asyncsafe-spin.h   |  12 +-
 modules/asyncsafe-spin |   8 +-
 4 files changed, 25 insertions(+), 325 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b903bacf5e..371af7fada 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2025-05-14  Bruno Haible  <br...@clisp.org>
+
+	asyncsafe-spin: Rely on module 'spin'.
+	* lib/asyncsafe-spin.h: Include glthread/spin.h.
+	(asyncsafe_spinlock_t, ASYNCSAFE_SPIN_INIT): Simplify by using
+	gl_spinlock_t, gl_spinlock_initializer.
+	* lib/asyncsafe-spin.c: Simplify by using glthread_spin_* functions.
+	* modules/asyncsafe-spin (Files): Remove m4/atomic-cas.m4.
+	(Depends-on): Add spin. Remove bool, windows-spin, sparcv8+.
+	(configure.ac): Remove tests.
+
 2025-05-14  Bruno Haible  <br...@clisp.org>
 
 	spin: Add tests.
diff --git a/lib/asyncsafe-spin.c b/lib/asyncsafe-spin.c
index e54f43ca7d..5dbefa1c60 100644
--- a/lib/asyncsafe-spin.c
+++ b/lib/asyncsafe-spin.c
@@ -22,332 +22,31 @@
 #include "asyncsafe-spin.h"
 
 #include <stdlib.h>
-#if defined _AIX
-# include <sys/atomic_op.h>
-#endif
-#if 0x590 <= __SUNPRO_C && __STDC__
-# define asm __asm
-#endif
-
-#if defined _WIN32 && ! defined __CYGWIN__
-/* Use Windows threads.  */
-
-void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  glwthread_spin_init (lock);
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  glwthread_spin_lock (lock);
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  if (glwthread_spin_unlock (lock))
-    abort ();
-}
-
-void
-asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
-{
-  glwthread_spin_destroy (lock);
-}
-
-#else
-
-# if HAVE_PTHREAD_H
-/* Use POSIX threads.  */
-
-/* We don't use semaphores (although sem_post() is allowed in signal handlers),
-   because it would require to link with -lrt on HP-UX 11, OSF/1, Solaris 10,
-   and also because on macOS only named semaphores work.
-
-   We don't use the C11 <stdatomic.h> (available in GCC >= 4.9) because it would
-   require to link with -latomic.  */
-
-#  if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) \
-       || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) \
-      && !defined __ibmxl__
-/* Use GCC built-ins (available in GCC >= 4.7 and clang >= 3.1) that operate on
-   the first byte of the lock.
-   Documentation:
-   <https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html>
- */
-
-#   if 1
-/* An implementation that verifies the unlocks.  */
 
 void
 asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
 {
-  __atomic_store_n (lock, 0, __ATOMIC_SEQ_CST);
+  glthread_spinlock_init (lock);
 }
 
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  /* Wait until *lock becomes 0, then replace it with 1.  */
-  asyncsafe_spinlock_t zero;
-  while (!(zero = 0,
-           __atomic_compare_exchange_n (lock, &zero, 1, false,
-                                        __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)))
-    ;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  /* If *lock is 1, then replace it with 0.  */
-  asyncsafe_spinlock_t one = 1;
-  if (!__atomic_compare_exchange_n (lock, &one, 0, false,
-                                    __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
-    abort ();
-}
-
-#   else
-/* An implementation that is a little bit more optimized, but does not verify
-   the unlocks.  */
-
 void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  __atomic_clear (lock, __ATOMIC_SEQ_CST);
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST))
-    ;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  __atomic_clear (lock, __ATOMIC_SEQ_CST);
-}
-
-#   endif
-
-#  elif (((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) \
-          || __clang_major__ >= 3) \
-         && HAVE_ATOMIC_COMPARE_AND_SWAP_GCC41)
-/* Use GCC built-ins (available on many platforms with GCC >= 4.1 or
-   clang >= 3.0).
-   Documentation:
-   <https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html>  */
-
-void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  *vp = 0;
-  __sync_synchronize ();
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  /* Wait until *lock becomes 0, then replace it with 1.  */
-  while (__sync_val_compare_and_swap (lock, 0, 1) != 0)
-    ;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  /* If *lock is 1, then replace it with 0.  */
-  if (__sync_val_compare_and_swap (lock, 1, 0) != 1)
-    abort ();
-}
-
-#  elif defined _AIX
-/* AIX */
-
-void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  atomic_p vp = (int *) lock;
-  _clear_lock (vp, 0);
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  atomic_p vp = (int *) lock;
-  while (_check_lock (vp, 0, 1))
-    ;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  atomic_p vp = (int *) lock;
-  if (_check_lock (vp, 1, 0))
-    abort ();
-}
-
-#  elif ((defined __GNUC__ || defined __clang__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)) || (defined __TINYC__ && (defined __i386 || defined __x86_64__))
-/* For older versions of GCC or clang, use inline assembly.
-   GCC, clang, and the Oracle Studio C 12 compiler understand GCC's extended
-   asm syntax, but the plain Oracle Studio C 11 compiler understands only
-   simple asm.  */
-/* An implementation that verifies the unlocks.  */
-
-static void
-memory_barrier (void)
-{
-#   if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
-#    if defined __i386 || defined __x86_64__
-#     if defined __TINYC__ && defined __i386
-  /* Cannot use the SSE instruction "mfence" with this compiler.  */
-  asm volatile ("lock orl $0,(%esp)");
-#     else
-  asm volatile ("mfence");
-#     endif
-#    endif
-#    if defined __sparc
-  asm volatile ("membar 2");
-#    endif
-#   else
-#    if defined __i386 || defined __x86_64__
-  asm ("mfence");
-#    endif
-#    if defined __sparc
-  asm ("membar 2");
-#    endif
-#   endif
-}
-
-/* Store NEWVAL in *VP if the old value *VP is == CMP.
-   Return the old value.  */
-static unsigned int
-atomic_compare_and_swap (volatile unsigned int *vp, unsigned int cmp,
-                         unsigned int newval)
+asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
+                     const sigset_t *mask, sigset_t *saved_mask)
 {
-#   if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
-  unsigned int oldval;
-#    if defined __i386 || defined __x86_64__
-  asm volatile (" lock\n cmpxchgl %3,(%1)"
-                : "=a" (oldval) : "r" (vp), "a" (cmp), "r" (newval) : "memory");
-#    endif
-#    if defined __sparc
-  asm volatile (" cas [%1],%2,%3\n"
-                " mov %3,%0"
-                : "=r" (oldval) : "r" (vp), "r" (cmp), "r" (newval) : "memory");
-#    endif
-  return oldval;
-#   else /* __SUNPRO_C */
-#    if defined __x86_64__
-  asm (" movl %esi,%eax\n"
-       " lock\n cmpxchgl %edx,(%rdi)");
-#    elif defined __i386
-  asm (" movl 16(%ebp),%ecx\n"
-       " movl 12(%ebp),%eax\n"
-       " movl 8(%ebp),%edx\n"
-       " lock\n cmpxchgl %ecx,(%edx)");
-#    endif
-#    if defined __sparc
-  asm (" cas [%i0],%i1,%i2\n"
-       " mov %i2,%i0");
-#    endif
-#   endif
+  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
+  glthread_spinlock_lock (lock);
 }
 
 void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  *vp = 0;
-  memory_barrier ();
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  while (atomic_compare_and_swap (vp, 0, 1) != 0)
-    ;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
+asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
 {
-  volatile unsigned int *vp = lock;
-  if (atomic_compare_and_swap (vp, 1, 0) != 1)
+  if (glthread_spinlock_unlock (lock))
     abort ();
+  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
 }
 
-#  else
-/* Fallback code.  It has some race conditions.  */
-
-void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  *vp = 0;
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  while (*vp)
-    ;
-  *vp = 1;
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-  volatile unsigned int *vp = lock;
-  *vp = 0;
-}
-
-#  endif
-
-# else
-/* Provide a dummy implementation for single-threaded applications.  */
-
-void
-asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
-{
-}
-
-static inline void
-do_lock (asyncsafe_spinlock_t *lock)
-{
-}
-
-static inline void
-do_unlock (asyncsafe_spinlock_t *lock)
-{
-}
-
-# endif
-
 void
 asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
 {
-}
-
-#endif
-
-void
-asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
-                     const sigset_t *mask, sigset_t *saved_mask)
-{
-  sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
-  do_lock (lock);
-}
-
-void
-asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
-{
-  do_unlock (lock);
-  sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
+  glthread_spinlock_destroy (lock);
 }
diff --git a/lib/asyncsafe-spin.h b/lib/asyncsafe-spin.h
index 8a8963877d..1bbf2f4702 100644
--- a/lib/asyncsafe-spin.h
+++ b/lib/asyncsafe-spin.h
@@ -42,14 +42,10 @@
 
 #include <signal.h>
 
-#if defined _WIN32 && ! defined __CYGWIN__
-# include "windows-spin.h"
-typedef glwthread_spinlock_t asyncsafe_spinlock_t;
-# define ASYNCSAFE_SPIN_INIT GLWTHREAD_SPIN_INIT
-#else
-typedef unsigned int asyncsafe_spinlock_t;
-# define ASYNCSAFE_SPIN_INIT 0
-#endif
+#include "glthread/spin.h"
+
+typedef gl_spinlock_t asyncsafe_spinlock_t;
+#define ASYNCSAFE_SPIN_INIT gl_spinlock_initializer
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/modules/asyncsafe-spin b/modules/asyncsafe-spin
index 3ca67464ef..d918096163 100644
--- a/modules/asyncsafe-spin
+++ b/modules/asyncsafe-spin
@@ -4,19 +4,13 @@ Spin locks for communication between threads and signal handlers.
 Files:
 lib/asyncsafe-spin.h
 lib/asyncsafe-spin.c
-m4/atomic-cas.m4
 
 Depends-on:
 signal-h
-bool
 sigprocmask
-windows-spin
-sparcv8+
+spin
 
 configure.ac:
-AC_REQUIRE([AC_C_INLINE])
-AC_CHECK_HEADERS_ONCE([pthread.h])
-AC_REQUIRE([gl_ATOMIC_COMPARE_AND_SWAP])
 
 Makefile.am:
 lib_SOURCES += asyncsafe-spin.c
-- 
2.43.0

Reply via email to