From: Bruno Haible Date: Wed, 14 May 2025 10:51:46 +0000 (+0200) Subject: spin: New module. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a258598ec7d20ad6a853ee2c5e811a35b506a1f2;p=thirdparty%2Fgnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index 85028d3684..6c37f8b1a4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2025-05-14 Bruno Haible + + 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 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 @@ 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{} @item @code{} +@item +@code{} @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 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 . */ + +/* Written by Bruno Haible , 2020. */ + +#include + +/* Specification. */ +#include "glthread/spin.h" + +#include +#if defined _AIX +# include +#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 (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: + + */ + +# 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: + */ + +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 . */ + +/* Written by Bruno Haible , 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 + +#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