/* * Copyright © 2011 Ryan Lortie * * This library 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 library 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 library; if not, see . * * Author: Ryan Lortie */ #include "config.h" #include "gatomic.h" /** * SECTION:atomic_operations * @title: Atomic Operations * @short_description: basic atomic integer and pointer operations * @see_also: #GMutex * * The following is a collection of compiler macros to provide atomic * access to integer and pointer-sized values. * * The macros that have 'int' in the name will operate on pointers to * #gint and #guint. The macros with 'pointer' in the name will operate * on pointers to any pointer-sized value, including #gsize. There is * no support for 64bit operations on platforms with 32bit pointers * because it is not generally possible to perform these operations * atomically. * * The get, set and exchange operations for integers and pointers * nominally operate on #gint and #gpointer, respectively. Of the * arithmetic operations, the 'add' operation operates on (and returns) * signed integer values (#gint and #gssize) and the 'and', 'or', and * 'xor' operations operate on (and return) unsigned integer values * (#guint and #gsize). * * All of the operations act as a full compiler and (where appropriate) * hardware memory barrier. Acquire and release or producer and * consumer barrier semantics are not available through this API. * * It is very important that all accesses to a particular integer or * pointer be performed using only this API and that different sizes of * operation are not mixed or used on overlapping memory regions. Never * read or assign directly from or to a value -- always use this API. * * For simple reference counting purposes you should use * g_atomic_int_inc() and g_atomic_int_dec_and_test(). Other uses that * fall outside of simple reference counting patterns are prone to * subtle bugs and occasionally undefined behaviour. It is also worth * noting that since all of these operations require global * synchronisation of the entire machine, they can be quite slow. In * the case of performing multiple atomic operations it can often be * faster to simply acquire a mutex lock around the critical area, * perform the operations normally and then release the lock. **/ /** * G_ATOMIC_LOCK_FREE: * * This macro is defined if the atomic operations of GLib are * implemented using real hardware atomic operations. This means that * the GLib atomic API can be used between processes and safely mixed * with other (hardware) atomic APIs. * * If this macro is not defined, the atomic operations may be * emulated using a mutex. In that case, the GLib atomic operations are * only atomic relative to themselves and within a single process. **/ /* NOTE CAREFULLY: * * This file is the lowest-level part of GLib. * * Other lowlevel parts of GLib (threads, slice allocator, g_malloc, * messages, etc) call into these functions and macros to get work done. * * As such, these functions can not call back into any part of GLib * without risking recursion. */ #ifdef G_ATOMIC_LOCK_FREE /* if G_ATOMIC_LOCK_FREE was defined by ./configure then we MUST * implement the atomic operations in a lock-free manner. */ #if defined (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) #if defined(__ATOMIC_SEQ_CST) && !defined(__clang__) /* The implementation used in this code path in gatomic.h assumes * 4-byte int */ G_STATIC_ASSERT (sizeof (gint) == 4); /* The implementations in gatomic.h assume 4- or 8-byte pointers */ G_STATIC_ASSERT (sizeof (void *) == 4 || sizeof (void *) == 8); #endif /** * g_atomic_int_get: * @atomic: a pointer to a #gint or #guint * * Gets the current value of @atomic. * * This call acts as a full compiler and hardware * memory barrier (before the get). * * Returns: the value of the integer * * Since: 2.4 **/ gint (g_atomic_int_get) (const volatile gint *atomic) { return g_atomic_int_get (atomic); } /** * g_atomic_int_set: * @atomic: a pointer to a #gint or #guint * @newval: a new value to store * * Sets the value of @atomic to @newval. * * This call acts as a full compiler and hardware * memory barrier (after the set). * * Since: 2.4 */ void (g_atomic_int_set) (volatile gint *atomic, gint newval) { g_atomic_int_set (atomic, newval); } /** * g_atomic_int_inc: * @atomic: a pointer to a #gint or #guint * * Increments the value of @atomic by 1. * * Think of this operation as an atomic version of `{ *atomic += 1; }`. * * This call acts as a full compiler and hardware memory barrier. * * Since: 2.4 **/ void (g_atomic_int_inc) (volatile gint *atomic) { g_atomic_int_inc (atomic); } /** * g_atomic_int_dec_and_test: * @atomic: a pointer to a #gint or #guint * * Decrements the value of @atomic by 1. * * Think of this operation as an atomic version of * `{ *atomic -= 1; return (*atomic == 0); }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: %TRUE if the resultant value is zero * * Since: 2.4 **/ gboolean (g_atomic_int_dec_and_test) (volatile gint *atomic) { return g_atomic_int_dec_and_test (atomic); } /** * g_atomic_int_compare_and_exchange: * @atomic: a pointer to a #gint or #guint * @oldval: the value to compare with * @newval: the value to conditionally replace with * * Compares @atomic to @oldval and, if equal, sets it to @newval. * If @atomic was not equal to @oldval then no change occurs. * * This compare and exchange is done atomically. * * Think of this operation as an atomic version of * `{ if (*atomic == oldval) { *atomic = newval; return TRUE; } else return FALSE; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: %TRUE if the exchange took place * * Since: 2.4 **/ gboolean (g_atomic_int_compare_and_exchange) (volatile gint *atomic, gint oldval, gint newval) { return g_atomic_int_compare_and_exchange (atomic, oldval, newval); } /** * g_atomic_int_add: * @atomic: a pointer to a #gint or #guint * @val: the value to add * * Atomically adds @val to the value of @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic += val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Before version 2.30, this function did not return a value * (but g_atomic_int_exchange_and_add() did, and had the same meaning). * * Returns: the value of @atomic before the add, signed * * Since: 2.4 **/ gint (g_atomic_int_add) (volatile gint *atomic, gint val) { return g_atomic_int_add (atomic, val); } /** * g_atomic_int_and: * @atomic: a pointer to a #gint or #guint * @val: the value to 'and' * * Performs an atomic bitwise 'and' of the value of @atomic and @val, * storing the result back in @atomic. * * This call acts as a full compiler and hardware memory barrier. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic &= val; return tmp; }`. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ guint (g_atomic_int_and) (volatile guint *atomic, guint val) { return g_atomic_int_and (atomic, val); } /** * g_atomic_int_or: * @atomic: a pointer to a #gint or #guint * @val: the value to 'or' * * Performs an atomic bitwise 'or' of the value of @atomic and @val, * storing the result back in @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic |= val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ guint (g_atomic_int_or) (volatile guint *atomic, guint val) { return g_atomic_int_or (atomic, val); } /** * g_atomic_int_xor: * @atomic: a pointer to a #gint or #guint * @val: the value to 'xor' * * Performs an atomic bitwise 'xor' of the value of @atomic and @val, * storing the result back in @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic ^= val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ guint (g_atomic_int_xor) (volatile guint *atomic, guint val) { return g_atomic_int_xor (atomic, val); } /** * g_atomic_pointer_get: * @atomic: (not nullable): a pointer to a #gpointer-sized value * * Gets the current value of @atomic. * * This call acts as a full compiler and hardware * memory barrier (before the get). * * Returns: the value of the pointer * * Since: 2.4 **/ gpointer (g_atomic_pointer_get) (const volatile void *atomic) { return g_atomic_pointer_get ((const volatile gpointer *) atomic); } /** * g_atomic_pointer_set: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @newval: a new value to store * * Sets the value of @atomic to @newval. * * This call acts as a full compiler and hardware * memory barrier (after the set). * * Since: 2.4 **/ void (g_atomic_pointer_set) (volatile void *atomic, gpointer newval) { g_atomic_pointer_set ((volatile gpointer *) atomic, newval); } /** * g_atomic_pointer_compare_and_exchange: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @oldval: the value to compare with * @newval: the value to conditionally replace with * * Compares @atomic to @oldval and, if equal, sets it to @newval. * If @atomic was not equal to @oldval then no change occurs. * * This compare and exchange is done atomically. * * Think of this operation as an atomic version of * `{ if (*atomic == oldval) { *atomic = newval; return TRUE; } else return FALSE; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: %TRUE if the exchange took place * * Since: 2.4 **/ gboolean (g_atomic_pointer_compare_and_exchange) (volatile void *atomic, gpointer oldval, gpointer newval) { return g_atomic_pointer_compare_and_exchange ((volatile gpointer *) atomic, oldval, newval); } /** * g_atomic_pointer_add: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @val: the value to add * * Atomically adds @val to the value of @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic += val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the add, signed * * Since: 2.30 **/ gssize (g_atomic_pointer_add) (volatile void *atomic, gssize val) { return g_atomic_pointer_add ((volatile gpointer *) atomic, val); } /** * g_atomic_pointer_and: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @val: the value to 'and' * * Performs an atomic bitwise 'and' of the value of @atomic and @val, * storing the result back in @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic &= val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ gsize (g_atomic_pointer_and) (volatile void *atomic, gsize val) { return g_atomic_pointer_and ((volatile gpointer *) atomic, val); } /** * g_atomic_pointer_or: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @val: the value to 'or' * * Performs an atomic bitwise 'or' of the value of @atomic and @val, * storing the result back in @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic |= val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ gsize (g_atomic_pointer_or) (volatile void *atomic, gsize val) { return g_atomic_pointer_or ((volatile gpointer *) atomic, val); } /** * g_atomic_pointer_xor: * @atomic: (not nullable): a pointer to a #gpointer-sized value * @val: the value to 'xor' * * Performs an atomic bitwise 'xor' of the value of @atomic and @val, * storing the result back in @atomic. * * Think of this operation as an atomic version of * `{ tmp = *atomic; *atomic ^= val; return tmp; }`. * * This call acts as a full compiler and hardware memory barrier. * * Returns: the value of @atomic before the operation, unsigned * * Since: 2.30 **/ gsize (g_atomic_pointer_xor) (volatile void *atomic, gsize val) { return g_atomic_pointer_xor ((volatile gpointer *) atomic, val); } #elif defined (G_PLATFORM_WIN32) #include #if !defined(_M_AMD64) && !defined (_M_IA64) && !defined(_M_X64) && !(defined _MSC_VER && _MSC_VER <= 1200) #define InterlockedAnd _InterlockedAnd #define InterlockedOr _InterlockedOr #define InterlockedXor _InterlockedXor #endif #if !defined (_MSC_VER) || _MSC_VER <= 1200 #include "gmessages.h" /* Inlined versions for older compiler */ static LONG _gInterlockedAnd (volatile guint *atomic, guint val) { LONG i, j; j = *atomic; do { i = j; j = InterlockedCompareExchange(atomic, i & val, i); } while (i != j); return j; } #define InterlockedAnd(a,b) _gInterlockedAnd(a,b) static LONG _gInterlockedOr (volatile guint *atomic, guint val) { LONG i, j; j = *atomic; do { i = j; j = InterlockedCompareExchange(atomic, i | val, i); } while (i != j); return j; } #define InterlockedOr(a,b) _gInterlockedOr(a,b) static LONG _gInterlockedXor (volatile guint *atomic, guint val) { LONG i, j; j = *atomic; do { i = j; j = InterlockedCompareExchange(atomic, i ^ val, i); } while (i != j); return j; } #define InterlockedXor(a,b) _gInterlockedXor(a,b) #endif /* * http://msdn.microsoft.com/en-us/library/ms684122(v=vs.85).aspx */ gint (g_atomic_int_get) (const volatile gint *atomic) { MemoryBarrier (); return *atomic; } void (g_atomic_int_set) (volatile gint *atomic, gint newval) { *atomic = newval; MemoryBarrier (); } void (g_atomic_int_inc) (volatile gint *atomic) { InterlockedIncrement (atomic); } gboolean (g_atomic_int_dec_and_test) (volatile gint *atomic) { return InterlockedDecrement (atomic) == 0; } gboolean (g_atomic_int_compare_and_exchange) (volatile gint *atomic, gint oldval, gint newval) { return InterlockedCompareExchange (atomic, newval, oldval) == oldval; } gint (g_atomic_int_add) (volatile gint *atomic, gint val) { return InterlockedExchangeAdd (atomic, val); } guint (g_atomic_int_and) (volatile guint *atomic, guint val) { return InterlockedAnd (atomic, val); } guint (g_atomic_int_or) (volatile guint *atomic, guint val) { return InterlockedOr (atomic, val); } guint (g_atomic_int_xor) (volatile guint *atomic, guint val) { return InterlockedXor (atomic, val); } gpointer (g_atomic_pointer_get) (const volatile void *atomic) { const volatile gpointer *ptr = atomic; MemoryBarrier (); return *ptr; } void (g_atomic_pointer_set) (volatile void *atomic, gpointer newval) { volatile gpointer *ptr = atomic; *ptr = newval; MemoryBarrier (); } gboolean (g_atomic_pointer_compare_and_exchange) (volatile void *atomic, gpointer oldval, gpointer newval) { return InterlockedCompareExchangePointer (atomic, newval, oldval) == oldval; } gssize (g_atomic_pointer_add) (volatile void *atomic, gssize val) { #if GLIB_SIZEOF_VOID_P == 8 return InterlockedExchangeAdd64 (atomic, val); #else return InterlockedExchangeAdd (atomic, val); #endif } gsize (g_atomic_pointer_and) (volatile void *atomic, gsize val) { #if GLIB_SIZEOF_VOID_P == 8 return InterlockedAnd64 (atomic, val); #else return InterlockedAnd (atomic, val); #endif } gsize (g_atomic_pointer_or) (volatile void *atomic, gsize val) { #if GLIB_SIZEOF_VOID_P == 8 return InterlockedOr64 (atomic, val); #else return InterlockedOr (atomic, val); #endif } gsize (g_atomic_pointer_xor) (volatile void *atomic, gsize val) { #if GLIB_SIZEOF_VOID_P == 8 return InterlockedXor64 (atomic, val); #else return InterlockedXor (atomic, val); #endif } #else /* This error occurs when ./configure decided that we should be capable * of lock-free atomics but we find at compile-time that we are not. */ #error G_ATOMIC_LOCK_FREE defined, but incapable of lock-free atomics. #endif /* defined (__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) */ #else /* G_ATOMIC_LOCK_FREE */ /* We are not permitted to call into any GLib functions from here, so we * can not use GMutex. * * Fortunately, we already take care of the Windows case above, and all * non-Windows platforms on which glib runs have pthreads. Use those. */ #include static pthread_mutex_t g_atomic_lock = PTHREAD_MUTEX_INITIALIZER; gint (g_atomic_int_get) (const volatile gint *atomic) { gint value; pthread_mutex_lock (&g_atomic_lock); value = *atomic; pthread_mutex_unlock (&g_atomic_lock); return value; } void (g_atomic_int_set) (volatile gint *atomic, gint value) { pthread_mutex_lock (&g_atomic_lock); *atomic = value; pthread_mutex_unlock (&g_atomic_lock); } void (g_atomic_int_inc) (volatile gint *atomic) { pthread_mutex_lock (&g_atomic_lock); (*atomic)++; pthread_mutex_unlock (&g_atomic_lock); } gboolean (g_atomic_int_dec_and_test) (volatile gint *atomic) { gboolean is_zero; pthread_mutex_lock (&g_atomic_lock); is_zero = --(*atomic) == 0; pthread_mutex_unlock (&g_atomic_lock); return is_zero; } gboolean (g_atomic_int_compare_and_exchange) (volatile gint *atomic, gint oldval, gint newval) { gboolean success; pthread_mutex_lock (&g_atomic_lock); if ((success = (*atomic == oldval))) *atomic = newval; pthread_mutex_unlock (&g_atomic_lock); return success; } gint (g_atomic_int_add) (volatile gint *atomic, gint val) { gint oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *atomic; *atomic = oldval + val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } guint (g_atomic_int_and) (volatile guint *atomic, guint val) { guint oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *atomic; *atomic = oldval & val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } guint (g_atomic_int_or) (volatile guint *atomic, guint val) { guint oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *atomic; *atomic = oldval | val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } guint (g_atomic_int_xor) (volatile guint *atomic, guint val) { guint oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *atomic; *atomic = oldval ^ val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } gpointer (g_atomic_pointer_get) (const volatile void *atomic) { const volatile gpointer *ptr = atomic; gpointer value; pthread_mutex_lock (&g_atomic_lock); value = *ptr; pthread_mutex_unlock (&g_atomic_lock); return value; } void (g_atomic_pointer_set) (volatile void *atomic, gpointer newval) { volatile gpointer *ptr = atomic; pthread_mutex_lock (&g_atomic_lock); *ptr = newval; pthread_mutex_unlock (&g_atomic_lock); } gboolean (g_atomic_pointer_compare_and_exchange) (volatile void *atomic, gpointer oldval, gpointer newval) { volatile gpointer *ptr = atomic; gboolean success; pthread_mutex_lock (&g_atomic_lock); if ((success = (*ptr == oldval))) *ptr = newval; pthread_mutex_unlock (&g_atomic_lock); return success; } gssize (g_atomic_pointer_add) (volatile void *atomic, gssize val) { volatile gssize *ptr = atomic; gssize oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *ptr; *ptr = oldval + val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } gsize (g_atomic_pointer_and) (volatile void *atomic, gsize val) { volatile gsize *ptr = atomic; gsize oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *ptr; *ptr = oldval & val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } gsize (g_atomic_pointer_or) (volatile void *atomic, gsize val) { volatile gsize *ptr = atomic; gsize oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *ptr; *ptr = oldval | val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } gsize (g_atomic_pointer_xor) (volatile void *atomic, gsize val) { volatile gsize *ptr = atomic; gsize oldval; pthread_mutex_lock (&g_atomic_lock); oldval = *ptr; *ptr = oldval ^ val; pthread_mutex_unlock (&g_atomic_lock); return oldval; } #endif /** * g_atomic_int_exchange_and_add: * @atomic: a pointer to a #gint * @val: the value to add * * This function existed before g_atomic_int_add() returned the prior * value of the integer (which it now does). It is retained only for * compatibility reasons. Don't use this function in new code. * * Returns: the value of @atomic before the add, signed * Since: 2.4 * Deprecated: 2.30: Use g_atomic_int_add() instead. **/ gint g_atomic_int_exchange_and_add (volatile gint *atomic, gint val) { return (g_atomic_int_add) (atomic, val); }