1  /* Spin locks for communication between threads and signal handlers.
       2     Copyright (C) 2020-2021 Free Software Foundation, Inc.
       3  
       4     This program is free software; you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation; either version 3, or (at your option)
       7     any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* Written by Bruno Haible <bruno@clisp.org>, 2020.  */
      18  
      19  #include <config.h>
      20  
      21  /* Specification.  */
      22  #include "asyncsafe-spin.h"
      23  
      24  #include <stdbool.h>
      25  #include <stdlib.h>
      26  #if defined _AIX
      27  # include <sys/atomic_op.h>
      28  #endif
      29  
      30  #if defined _WIN32 && ! defined __CYGWIN__
      31  /* Use Windows threads.  */
      32  
      33  void
      34  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
      35  {
      36    glwthread_spin_init (lock);
      37  }
      38  
      39  static inline void
      40  do_lock (asyncsafe_spinlock_t *lock)
      41  {
      42    glwthread_spin_lock (lock);
      43  }
      44  
      45  static inline void
      46  do_unlock (asyncsafe_spinlock_t *lock)
      47  {
      48    if (glwthread_spin_unlock (lock))
      49      abort ();
      50  }
      51  
      52  void
      53  asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
      54  {
      55    glwthread_spin_destroy (lock);
      56  }
      57  
      58  #else
      59  
      60  # if HAVE_PTHREAD_H
      61  /* Use POSIX threads.  */
      62  
      63  /* We don't use semaphores (although sem_post() is allowed in signal handlers),
      64     because it would require to link with -lrt on HP-UX 11, OSF/1, Solaris 10,
      65     and also because on macOS only named semaphores work.
      66  
      67     We don't use the C11 <stdatomic.h> (available in GCC >= 4.9) because it would
      68     require to link with -latomic.  */
      69  
      70  #  if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) \
      71         || __clang_major > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) \
      72        && !defined __ibmxl__
      73  /* Use GCC built-ins (available in GCC >= 4.7 and clang >= 3.1) that operate on
      74     the first byte of the lock.
      75     Documentation:
      76     <https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html>
      77   */
      78  
      79  #   if 1
      80  /* An implementation that verifies the unlocks.  */
      81  
      82  void
      83  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
      84  {
      85    __atomic_store_n (lock, 0, __ATOMIC_SEQ_CST);
      86  }
      87  
      88  static inline void
      89  do_lock (asyncsafe_spinlock_t *lock)
      90  {
      91    /* Wait until *lock becomes 0, then replace it with 1.  */
      92    asyncsafe_spinlock_t zero;
      93    while (!(zero = 0,
      94             __atomic_compare_exchange_n (lock, &zero, 1, false,
      95                                          __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)))
      96      ;
      97  }
      98  
      99  static inline void
     100  do_unlock (asyncsafe_spinlock_t *lock)
     101  {
     102    /* If *lock is 1, then replace it with 0.  */
     103    asyncsafe_spinlock_t one = 1;
     104    if (!__atomic_compare_exchange_n (lock, &one, 0, false,
     105                                      __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
     106      abort ();
     107  }
     108  
     109  #   else
     110  /* An implementation that is a little bit more optimized, but does not verify
     111     the unlocks.  */
     112  
     113  void
     114  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     115  {
     116    __atomic_clear (lock, __ATOMIC_SEQ_CST);
     117  }
     118  
     119  static inline void
     120  do_lock (asyncsafe_spinlock_t *lock)
     121  {
     122    while (__atomic_test_and_set (lock, __ATOMIC_SEQ_CST))
     123      ;
     124  }
     125  
     126  static inline void
     127  do_unlock (asyncsafe_spinlock_t *lock)
     128  {
     129    __atomic_clear (lock, __ATOMIC_SEQ_CST);
     130  }
     131  
     132  #   endif
     133  
     134  #  elif (((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) \
     135            && !defined __sparc__) \
     136           || __clang_major__ >= 3) \
     137          && !defined __ibmxl__
     138  /* Use GCC built-ins (available in GCC >= 4.1, except on SPARC, and
     139     clang >= 3.0).
     140     Documentation:
     141     <https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html>  */
     142  
     143  void
     144  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     145  {
     146    volatile unsigned int *vp = lock;
     147    *vp = 0;
     148    __sync_synchronize ();
     149  }
     150  
     151  static inline void
     152  do_lock (asyncsafe_spinlock_t *lock)
     153  {
     154    /* Wait until *lock becomes 0, then replace it with 1.  */
     155    while (__sync_val_compare_and_swap (lock, 0, 1) != 0)
     156      ;
     157  }
     158  
     159  static inline void
     160  do_unlock (asyncsafe_spinlock_t *lock)
     161  {
     162    /* If *lock is 1, then replace it with 0.  */
     163    if (__sync_val_compare_and_swap (lock, 1, 0) != 1)
     164      abort ();
     165  }
     166  
     167  #  elif defined _AIX
     168  /* AIX */
     169  
     170  void
     171  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     172  {
     173    atomic_p vp = (int *) lock;
     174    _clear_lock (vp, 0);
     175  }
     176  
     177  static inline void
     178  do_lock (asyncsafe_spinlock_t *lock)
     179  {
     180    atomic_p vp = (int *) lock;
     181    while (_check_lock (vp, 0, 1))
     182      ;
     183  }
     184  
     185  static inline void
     186  do_unlock (asyncsafe_spinlock_t *lock)
     187  {
     188    atomic_p vp = (int *) lock;
     189    if (_check_lock (vp, 1, 0))
     190      abort ();
     191  }
     192  
     193  #  elif ((defined __GNUC__ || defined __clang__ || defined __SUNPRO_C) && (defined __sparc || defined __i386 || defined __x86_64__)) || (defined __TINYC__ && (defined __i386 || defined __x86_64__))
     194  /* For older versions of GCC or clang, use inline assembly.
     195     GCC, clang, and the Oracle Studio C 12 compiler understand GCC's extended
     196     asm syntax, but the plain Oracle Studio C 11 compiler understands only
     197     simple asm.  */
     198  /* An implementation that verifies the unlocks.  */
     199  
     200  static void
     201  memory_barrier (void)
     202  {
     203  #   if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
     204  #    if defined __i386 || defined __x86_64__
     205  #     if defined __TINYC__ && defined __i386
     206    /* Cannot use the SSE instruction "mfence" with this compiler.  */
     207    asm volatile ("lock orl $0,(%esp)");
     208  #     else
     209    asm volatile ("mfence");
     210  #     endif
     211  #    endif
     212  #    if defined __sparc
     213    asm volatile ("membar 2");
     214  #    endif
     215  #   else
     216  #    if defined __i386 || defined __x86_64__
     217    asm ("mfence");
     218  #    endif
     219  #    if defined __sparc
     220    asm ("membar 2");
     221  #    endif
     222  #   endif
     223  }
     224  
     225  /* Store NEWVAL in *VP if the old value *VP is == CMP.
     226     Return the old value.  */
     227  static unsigned int
     228  atomic_compare_and_swap (volatile unsigned int *vp, unsigned int cmp,
     229                           unsigned int newval)
     230  {
     231  #   if defined __GNUC__ || defined __clang__ || __SUNPRO_C >= 0x590 || defined __TINYC__
     232    unsigned int oldval;
     233  #    if defined __i386 || defined __x86_64__
     234    asm volatile (" lock\n cmpxchgl %3,(%1)"
     235                  : "=a" (oldval) : "r" (vp), "a" (cmp), "r" (newval) : "memory");
     236  #    endif
     237  #    if defined __sparc
     238    asm volatile (" cas [%1],%2,%3\n"
     239                  " mov %3,%0"
     240                  : "=r" (oldval) : "r" (vp), "r" (cmp), "r" (newval) : "memory");
     241  #    endif
     242    return oldval;
     243  #   else /* __SUNPRO_C */
     244  #    if defined __x86_64__
     245    asm (" movl %esi,%eax\n"
     246         " lock\n cmpxchgl %edx,(%rdi)");
     247  #    elif defined __i386
     248    asm (" movl 16(%ebp),%ecx\n"
     249         " movl 12(%ebp),%eax\n"
     250         " movl 8(%ebp),%edx\n"
     251         " lock\n cmpxchgl %ecx,(%edx)");
     252  #    endif
     253  #    if defined __sparc
     254    asm (" cas [%i0],%i1,%i2\n"
     255         " mov %i2,%i0");
     256  #    endif
     257  #   endif
     258  }
     259  
     260  void
     261  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     262  {
     263    volatile unsigned int *vp = lock;
     264    *vp = 0;
     265    memory_barrier ();
     266  }
     267  
     268  static inline void
     269  do_lock (asyncsafe_spinlock_t *lock)
     270  {
     271    volatile unsigned int *vp = lock;
     272    while (atomic_compare_and_swap (vp, 0, 1) != 0)
     273      ;
     274  }
     275  
     276  static inline void
     277  do_unlock (asyncsafe_spinlock_t *lock)
     278  {
     279    volatile unsigned int *vp = lock;
     280    if (atomic_compare_and_swap (vp, 1, 0) != 1)
     281      abort ();
     282  }
     283  
     284  #  else
     285  /* Fallback code.  It has some race conditions.  */
     286  
     287  void
     288  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     289  {
     290    volatile unsigned int *vp = lock;
     291    *vp = 0;
     292  }
     293  
     294  static inline void
     295  do_lock (asyncsafe_spinlock_t *lock)
     296  {
     297    volatile unsigned int *vp = lock;
     298    while (*vp)
     299      ;
     300    *vp = 1;
     301  }
     302  
     303  static inline void
     304  do_unlock (asyncsafe_spinlock_t *lock)
     305  {
     306    volatile unsigned int *vp = lock;
     307    *vp = 0;
     308  }
     309  
     310  #  endif
     311  
     312  # else
     313  /* Provide a dummy implementation for single-threaded applications.  */
     314  
     315  void
     316  asyncsafe_spin_init (asyncsafe_spinlock_t *lock)
     317  {
     318  }
     319  
     320  static inline void
     321  do_lock (asyncsafe_spinlock_t *lock)
     322  {
     323  }
     324  
     325  static inline void
     326  do_unlock (asyncsafe_spinlock_t *lock)
     327  {
     328  }
     329  
     330  # endif
     331  
     332  void
     333  asyncsafe_spin_destroy (asyncsafe_spinlock_t *lock)
     334  {
     335  }
     336  
     337  #endif
     338  
     339  void
     340  asyncsafe_spin_lock (asyncsafe_spinlock_t *lock,
     341                       const sigset_t *mask, sigset_t *saved_mask)
     342  {
     343    sigprocmask (SIG_BLOCK, mask, saved_mask); /* equivalent to pthread_sigmask */
     344    do_lock (lock);
     345  }
     346  
     347  void
     348  asyncsafe_spin_unlock (asyncsafe_spinlock_t *lock, const sigset_t *saved_mask)
     349  {
     350    do_unlock (lock);
     351    sigprocmask (SIG_SETMASK, saved_mask, NULL); /* equivalent to pthread_sigmask */
     352  }