]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
spin: New module.
authorBruno Haible <bruno@clisp.org>
Wed, 14 May 2025 10:51:46 +0000 (12:51 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 14 May 2025 11:16:07 +0000 (13:16 +0200)
* 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
doc/multithread.texi
lib/glthread/spin.c [new file with mode: 0644]
lib/glthread/spin.h [new file with mode: 0644]
modules/bind-tests
modules/spin [new file with mode: 0644]

index 85028d3684cd784f614df9b9a647f395e3f0596b..6c37f8b1a4e1a3a60ad13419999673b2fa3ffe1e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2025-05-14  Bruno Haible  <bruno@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  <bruno@clisp.org>
 
        file-has-acl: Fix compilation error on Solaris (regression 2025-05-09).
index b24c35a207b6640d8a321966af974502325d2434..1316404bef4d56e23fe718676e4648ed9e1275c4 100644 (file)
@@ -107,7 +107,7 @@ clumsy: You have to initialize it through a once-only function.
 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 @@ The Gnulib multithreading API is documented in the respective include files:
 @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 @@ To make use of Gnulib multithreading, use the following Gnulib modules:
 @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 @@ To make use of Gnulib multithreading, use the following Gnulib modules:
 @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 (file)
index 0000000..c8dd483
--- /dev/null
@@ -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 <bruno@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 (file)
index 0000000..8a2405f
--- /dev/null
@@ -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 <bruno@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 */
index 3d2971e55cf772081f28ccfc6cfc72bd49c48999..f7b5a4782a721c23a8253241670837bd3986fcdf 100644 (file)
@@ -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 (file)
index 0000000..756c7b5
--- /dev/null
@@ -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