mirror of https://gitee.com/openkylin/glib2.0.git
697 lines
20 KiB
C
697 lines
20 KiB
C
/*
|
|
* Copyright © 2008 Ryan Lortie
|
|
* Copyright © 2010 Codethink Limited
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gbitlock.h"
|
|
|
|
#include <glib/gmacros.h>
|
|
#include <glib/gmessages.h>
|
|
#include <glib/gatomic.h>
|
|
#include <glib/gslist.h>
|
|
#include <glib/gthread.h>
|
|
#include <glib/gslice.h>
|
|
|
|
#include "gthreadprivate.h"
|
|
|
|
#ifdef G_BIT_LOCK_FORCE_FUTEX_EMULATION
|
|
#undef HAVE_FUTEX
|
|
#undef HAVE_FUTEX_TIME64
|
|
#endif
|
|
|
|
#ifndef HAVE_FUTEX
|
|
static GMutex g_futex_mutex;
|
|
static GSList *g_futex_address_list = NULL;
|
|
#endif
|
|
|
|
#if defined(HAVE_FUTEX) || defined(HAVE_FUTEX_TIME64)
|
|
/*
|
|
* We have headers for futex(2) on the build machine. This does not
|
|
* imply that every system that ever runs the resulting glib will have
|
|
* kernel support for futex, but you'd have to have a pretty old
|
|
* kernel in order for that not to be the case.
|
|
*
|
|
* If anyone actually gets bit by this, please file a bug. :)
|
|
*/
|
|
|
|
/* < private >
|
|
* g_futex_wait:
|
|
* @address: a pointer to an integer
|
|
* @value: the value that should be at @address
|
|
*
|
|
* Atomically checks that the value stored at @address is equal to
|
|
* @value and then blocks. If the value stored at @address is not
|
|
* equal to @value then this function returns immediately.
|
|
*
|
|
* To unblock, call g_futex_wake() on @address.
|
|
*
|
|
* This call may spuriously unblock (for example, in response to the
|
|
* process receiving a signal) but this is not guaranteed. Unlike the
|
|
* Linux system call of a similar name, there is no guarantee that a
|
|
* waiting process will unblock due to a g_futex_wake() call in a
|
|
* separate process.
|
|
*/
|
|
static void
|
|
g_futex_wait (const gint *address,
|
|
gint value)
|
|
{
|
|
g_futex_simple (address, (gsize) FUTEX_WAIT_PRIVATE, (gsize) value, NULL);
|
|
}
|
|
|
|
/* < private >
|
|
* g_futex_wake:
|
|
* @address: a pointer to an integer
|
|
*
|
|
* Nominally, wakes one thread that is blocked in g_futex_wait() on
|
|
* @address (if any thread is currently waiting).
|
|
*
|
|
* As mentioned in the documentation for g_futex_wait(), spurious
|
|
* wakeups may occur. As such, this call may result in more than one
|
|
* thread being woken up.
|
|
*/
|
|
static void
|
|
g_futex_wake (const gint *address)
|
|
{
|
|
g_futex_simple (address, (gsize) FUTEX_WAKE_PRIVATE, (gsize) 1, NULL);
|
|
}
|
|
|
|
#else
|
|
|
|
/* emulate futex(2) */
|
|
typedef struct
|
|
{
|
|
const gint *address;
|
|
gint ref_count;
|
|
GCond wait_queue;
|
|
} WaitAddress;
|
|
|
|
static WaitAddress *
|
|
g_futex_find_address (const gint *address)
|
|
{
|
|
GSList *node;
|
|
|
|
for (node = g_futex_address_list; node; node = node->next)
|
|
{
|
|
WaitAddress *waiter = node->data;
|
|
|
|
if (waiter->address == address)
|
|
return waiter;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
g_futex_wait (const gint *address,
|
|
gint value)
|
|
{
|
|
g_mutex_lock (&g_futex_mutex);
|
|
if G_LIKELY (g_atomic_int_get (address) == value)
|
|
{
|
|
WaitAddress *waiter;
|
|
|
|
if ((waiter = g_futex_find_address (address)) == NULL)
|
|
{
|
|
waiter = g_slice_new (WaitAddress);
|
|
waiter->address = address;
|
|
g_cond_init (&waiter->wait_queue);
|
|
waiter->ref_count = 0;
|
|
g_futex_address_list =
|
|
g_slist_prepend (g_futex_address_list, waiter);
|
|
}
|
|
|
|
waiter->ref_count++;
|
|
g_cond_wait (&waiter->wait_queue, &g_futex_mutex);
|
|
|
|
if (!--waiter->ref_count)
|
|
{
|
|
g_futex_address_list =
|
|
g_slist_remove (g_futex_address_list, waiter);
|
|
g_cond_clear (&waiter->wait_queue);
|
|
g_slice_free (WaitAddress, waiter);
|
|
}
|
|
}
|
|
g_mutex_unlock (&g_futex_mutex);
|
|
}
|
|
|
|
static void
|
|
g_futex_wake (const gint *address)
|
|
{
|
|
WaitAddress *waiter;
|
|
|
|
/* need to lock here for two reasons:
|
|
* 1) need to acquire/release lock to ensure waiter is not in
|
|
* the process of registering a wait
|
|
* 2) need to -stay- locked until the end to ensure a wake()
|
|
* in another thread doesn't cause 'waiter' to stop existing
|
|
*/
|
|
g_mutex_lock (&g_futex_mutex);
|
|
if ((waiter = g_futex_find_address (address)))
|
|
g_cond_signal (&waiter->wait_queue);
|
|
g_mutex_unlock (&g_futex_mutex);
|
|
}
|
|
#endif
|
|
|
|
#define CONTENTION_CLASSES 11
|
|
static gint g_bit_lock_contended[CONTENTION_CLASSES]; /* (atomic) */
|
|
|
|
G_ALWAYS_INLINE static inline guint
|
|
bit_lock_contended_class (gpointer address)
|
|
{
|
|
return ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
|
}
|
|
|
|
#if (defined (i386) || defined (__amd64__))
|
|
#if G_GNUC_CHECK_VERSION(4, 5)
|
|
#define USE_ASM_GOTO 1
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* g_bit_lock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* Sets the indicated @lock_bit in @address. If the bit is already
|
|
* set, this call will block until g_bit_unlock() unsets the
|
|
* corresponding bit.
|
|
*
|
|
* Attempting to lock on two different bits within the same integer is
|
|
* not supported and will very probably cause deadlocks.
|
|
*
|
|
* The value of the bit that is set is (1u << @bit). If @bit is not
|
|
* between 0 and 31 then the result is undefined.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably. While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
void
|
|
g_bit_lock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
gint *address_nonvolatile = (gint *) address;
|
|
|
|
#ifdef USE_ASM_GOTO
|
|
retry:
|
|
__asm__ volatile goto ("lock bts %1, (%0)\n"
|
|
"jc %l[contended]"
|
|
: /* no output */
|
|
: "r" (address), "r" (lock_bit)
|
|
: "cc", "memory"
|
|
: contended);
|
|
return;
|
|
|
|
contended:
|
|
{
|
|
guint mask = 1u << lock_bit;
|
|
guint v;
|
|
|
|
v = (guint) g_atomic_int_get (address_nonvolatile);
|
|
if (v & mask)
|
|
{
|
|
guint class = bit_lock_contended_class (address_nonvolatile);
|
|
|
|
g_atomic_int_add (&g_bit_lock_contended[class], +1);
|
|
g_futex_wait (address_nonvolatile, v);
|
|
g_atomic_int_add (&g_bit_lock_contended[class], -1);
|
|
}
|
|
}
|
|
goto retry;
|
|
#else
|
|
guint mask = 1u << lock_bit;
|
|
guint v;
|
|
|
|
retry:
|
|
v = g_atomic_int_or (address_nonvolatile, mask);
|
|
if (v & mask)
|
|
/* already locked */
|
|
{
|
|
guint class = bit_lock_contended_class (address_nonvolatile);
|
|
|
|
g_atomic_int_add (&g_bit_lock_contended[class], +1);
|
|
g_futex_wait (address_nonvolatile, v);
|
|
g_atomic_int_add (&g_bit_lock_contended[class], -1);
|
|
|
|
goto retry;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* g_bit_trylock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* Sets the indicated @lock_bit in @address, returning %TRUE if
|
|
* successful. If the bit is already set, returns %FALSE immediately.
|
|
*
|
|
* Attempting to lock on two different bits within the same integer is
|
|
* not supported.
|
|
*
|
|
* The value of the bit that is set is (1u << @bit). If @bit is not
|
|
* between 0 and 31 then the result is undefined.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably. While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Returns: %TRUE if the lock was acquired
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
gboolean
|
|
g_bit_trylock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
#ifdef USE_ASM_GOTO
|
|
gboolean result;
|
|
|
|
__asm__ volatile ("lock bts %2, (%1)\n"
|
|
"setnc %%al\n"
|
|
"movzx %%al, %0"
|
|
: "=r" (result)
|
|
: "r" (address), "r" (lock_bit)
|
|
: "cc", "memory");
|
|
|
|
return result;
|
|
#else
|
|
gint *address_nonvolatile = (gint *) address;
|
|
guint mask = 1u << lock_bit;
|
|
guint v;
|
|
|
|
v = g_atomic_int_or (address_nonvolatile, mask);
|
|
|
|
return ~v & mask;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* g_bit_unlock:
|
|
* @address: a pointer to an integer
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* Clears the indicated @lock_bit in @address. If another thread is
|
|
* currently blocked in g_bit_lock() on this same bit then it will be
|
|
* woken up.
|
|
*
|
|
* This function accesses @address atomically. All other accesses to
|
|
* @address must be atomic in order for this function to work
|
|
* reliably. While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Since: 2.24
|
|
**/
|
|
void
|
|
g_bit_unlock (volatile gint *address,
|
|
gint lock_bit)
|
|
{
|
|
gint *address_nonvolatile = (gint *) address;
|
|
|
|
#ifdef USE_ASM_GOTO
|
|
__asm__ volatile ("lock btr %1, (%0)"
|
|
: /* no output */
|
|
: "r" (address), "r" (lock_bit)
|
|
: "cc", "memory");
|
|
#else
|
|
guint mask = 1u << lock_bit;
|
|
|
|
g_atomic_int_and (address_nonvolatile, ~mask);
|
|
#endif
|
|
|
|
{
|
|
guint class = bit_lock_contended_class (address_nonvolatile);
|
|
|
|
if (g_atomic_int_get (&g_bit_lock_contended[class]))
|
|
g_futex_wake (address_nonvolatile);
|
|
}
|
|
}
|
|
|
|
|
|
/* We emulate pointer-sized futex(2) because the kernel API only
|
|
* supports integers.
|
|
*
|
|
* We assume that the 'interesting' part is always the lower order bits.
|
|
* This assumption holds because pointer bitlocks are restricted to
|
|
* using the low order bits of the pointer as the lock.
|
|
*
|
|
* On 32 bits, there is nothing to do since the pointer size is equal to
|
|
* the integer size. On little endian the lower-order bits don't move,
|
|
* so do nothing. Only on 64bit big endian do we need to do a bit of
|
|
* pointer arithmetic: the low order bits are shifted by 4 bytes. We
|
|
* have a helper function that always does the right thing here.
|
|
*
|
|
* Since we always consider the low-order bits of the integer value, a
|
|
* simple cast from (gsize) to (guint) always takes care of that.
|
|
*
|
|
* After that, pointer-sized futex becomes as simple as:
|
|
*
|
|
* g_futex_wait (g_futex_int_address (address), (guint) value);
|
|
*
|
|
* and
|
|
*
|
|
* g_futex_wake (g_futex_int_address (int_address));
|
|
*/
|
|
static const gint *
|
|
g_futex_int_address (const void *address)
|
|
{
|
|
const gint *int_address = address;
|
|
|
|
/* this implementation makes these (reasonable) assumptions: */
|
|
G_STATIC_ASSERT (G_BYTE_ORDER == G_LITTLE_ENDIAN ||
|
|
(G_BYTE_ORDER == G_BIG_ENDIAN &&
|
|
sizeof (int) == 4 &&
|
|
(sizeof (gpointer) == 4 || sizeof (gpointer) == 8)));
|
|
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN && GLIB_SIZEOF_VOID_P == 8
|
|
int_address++;
|
|
#endif
|
|
|
|
return int_address;
|
|
}
|
|
|
|
G_ALWAYS_INLINE static inline gpointer
|
|
pointer_bit_lock_mask_ptr (gpointer ptr, guint lock_bit, gboolean set, guintptr preserve_mask, gpointer preserve_ptr)
|
|
{
|
|
guintptr x_ptr;
|
|
guintptr x_preserve_ptr;
|
|
guintptr lock_mask;
|
|
|
|
x_ptr = (guintptr) ptr;
|
|
|
|
if (preserve_mask != 0)
|
|
{
|
|
x_preserve_ptr = (guintptr) preserve_ptr;
|
|
x_ptr = (x_preserve_ptr & preserve_mask) | (x_ptr & ~preserve_mask);
|
|
}
|
|
|
|
if (lock_bit == G_MAXUINT)
|
|
return (gpointer) x_ptr;
|
|
|
|
lock_mask = (guintptr) (1u << lock_bit);
|
|
if (set)
|
|
return (gpointer) (x_ptr | lock_mask);
|
|
else
|
|
return (gpointer) (x_ptr & ~lock_mask);
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_lock_and_get:
|
|
* @address: (not nullable): a pointer to a #gpointer-sized value
|
|
* @lock_bit: a bit value between 0 and 31
|
|
* @out_ptr: (out) (optional): returns the set pointer atomically.
|
|
* This is the value after setting the lock, it thus always has the
|
|
* lock bit set, while previously @address had the lockbit unset.
|
|
* You may also use g_pointer_bit_lock_mask_ptr() to clear the lock bit.
|
|
*
|
|
* This is equivalent to g_bit_lock, but working on pointers (or other
|
|
* pointer-sized values).
|
|
*
|
|
* For portability reasons, you may only lock on the bottom 32 bits of
|
|
* the pointer.
|
|
*
|
|
* Since: 2.80
|
|
**/
|
|
void
|
|
(g_pointer_bit_lock_and_get) (gpointer address,
|
|
guint lock_bit,
|
|
guintptr *out_ptr)
|
|
{
|
|
guint class = bit_lock_contended_class (address);
|
|
guintptr mask;
|
|
guintptr v;
|
|
|
|
g_return_if_fail (lock_bit < 32);
|
|
|
|
mask = 1u << lock_bit;
|
|
|
|
#ifdef USE_ASM_GOTO
|
|
if (G_LIKELY (!out_ptr))
|
|
{
|
|
while (TRUE)
|
|
{
|
|
__asm__ volatile goto ("lock bts %1, (%0)\n"
|
|
"jc %l[contended]"
|
|
: /* no output */
|
|
: "r"(address), "r"((gsize) lock_bit)
|
|
: "cc", "memory"
|
|
: contended);
|
|
return;
|
|
|
|
contended:
|
|
v = (guintptr) g_atomic_pointer_get ((gpointer *) address);
|
|
if (v & mask)
|
|
{
|
|
g_atomic_int_add (&g_bit_lock_contended[class], +1);
|
|
g_futex_wait (g_futex_int_address (address), v);
|
|
g_atomic_int_add (&g_bit_lock_contended[class], -1);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
retry:
|
|
v = g_atomic_pointer_or ((gpointer *) address, mask);
|
|
if (v & mask)
|
|
/* already locked */
|
|
{
|
|
g_atomic_int_add (&g_bit_lock_contended[class], +1);
|
|
g_futex_wait (g_futex_int_address (address), (guint) v);
|
|
g_atomic_int_add (&g_bit_lock_contended[class], -1);
|
|
goto retry;
|
|
}
|
|
|
|
if (out_ptr)
|
|
*out_ptr = (v | mask);
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_lock:
|
|
* @address: (not nullable): a pointer to a #gpointer-sized value
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* This is equivalent to g_bit_lock, but working on pointers (or other
|
|
* pointer-sized values).
|
|
*
|
|
* For portability reasons, you may only lock on the bottom 32 bits of
|
|
* the pointer.
|
|
*
|
|
* While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Since: 2.30
|
|
**/
|
|
void
|
|
(g_pointer_bit_lock) (volatile void *address,
|
|
gint lock_bit)
|
|
{
|
|
g_pointer_bit_lock_and_get ((gpointer *) address, (guint) lock_bit, NULL);
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_trylock:
|
|
* @address: (not nullable): a pointer to a #gpointer-sized value
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* This is equivalent to g_bit_trylock(), but working on pointers (or
|
|
* other pointer-sized values).
|
|
*
|
|
* For portability reasons, you may only lock on the bottom 32 bits of
|
|
* the pointer.
|
|
*
|
|
* While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Returns: %TRUE if the lock was acquired
|
|
*
|
|
* Since: 2.30
|
|
**/
|
|
gboolean
|
|
(g_pointer_bit_trylock) (volatile void *address,
|
|
gint lock_bit)
|
|
{
|
|
g_return_val_if_fail (lock_bit < 32, FALSE);
|
|
|
|
{
|
|
#ifdef USE_ASM_GOTO
|
|
gboolean result;
|
|
|
|
__asm__ volatile ("lock bts %2, (%1)\n"
|
|
"setnc %%al\n"
|
|
"movzx %%al, %0"
|
|
: "=r" (result)
|
|
: "r" (address), "r" ((gsize) lock_bit)
|
|
: "cc", "memory");
|
|
|
|
return result;
|
|
#else
|
|
void *address_nonvolatile = (void *) address;
|
|
gpointer *pointer_address = address_nonvolatile;
|
|
gsize mask = 1u << lock_bit;
|
|
guintptr v;
|
|
|
|
g_return_val_if_fail (lock_bit < 32, FALSE);
|
|
|
|
v = g_atomic_pointer_or (pointer_address, mask);
|
|
|
|
return (~(gsize) v & mask) != 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_unlock:
|
|
* @address: (not nullable): a pointer to a #gpointer-sized value
|
|
* @lock_bit: a bit value between 0 and 31
|
|
*
|
|
* This is equivalent to g_bit_unlock, but working on pointers (or other
|
|
* pointer-sized values).
|
|
*
|
|
* For portability reasons, you may only lock on the bottom 32 bits of
|
|
* the pointer.
|
|
*
|
|
* While @address has a `volatile` qualifier, this is a historical
|
|
* artifact and the argument passed to it should not be `volatile`.
|
|
*
|
|
* Since: 2.30
|
|
**/
|
|
void
|
|
(g_pointer_bit_unlock) (volatile void *address,
|
|
gint lock_bit)
|
|
{
|
|
void *address_nonvolatile = (void *) address;
|
|
|
|
g_return_if_fail (lock_bit < 32);
|
|
|
|
{
|
|
#ifdef USE_ASM_GOTO
|
|
__asm__ volatile ("lock btr %1, (%0)"
|
|
: /* no output */
|
|
: "r" (address), "r" ((gsize) lock_bit)
|
|
: "cc", "memory");
|
|
#else
|
|
gpointer *pointer_address = address_nonvolatile;
|
|
gsize mask = 1u << lock_bit;
|
|
|
|
g_atomic_pointer_and (pointer_address, ~mask);
|
|
#endif
|
|
|
|
{
|
|
guint class = bit_lock_contended_class (address_nonvolatile);
|
|
|
|
if (g_atomic_int_get (&g_bit_lock_contended[class]))
|
|
g_futex_wake (g_futex_int_address (address_nonvolatile));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_lock_mask_ptr:
|
|
* @ptr: (nullable): the pointer to mask
|
|
* @lock_bit: the bit to set/clear. If set to `G_MAXUINT`, the
|
|
* lockbit is taken from @preserve_ptr or @ptr (depending on @preserve_mask).
|
|
* @set: whether to set (lock) the bit or unset (unlock). This
|
|
* has no effect, if @lock_bit is set to `G_MAXUINT`.
|
|
* @preserve_mask: if non-zero, a bit-mask for @preserve_ptr. The
|
|
* @preserve_mask bits from @preserve_ptr are set in the result.
|
|
* Note that the @lock_bit bit will be always set according to @set,
|
|
* regardless of @preserve_mask and @preserve_ptr (unless @lock_bit is
|
|
* `G_MAXUINT`).
|
|
* @preserve_ptr: (nullable): if @preserve_mask is non-zero, the bits
|
|
* from this pointer are set in the result.
|
|
*
|
|
* This mangles @ptr as g_pointer_bit_lock() and g_pointer_bit_unlock()
|
|
* do.
|
|
*
|
|
* Returns: the mangled pointer.
|
|
*
|
|
* Since: 2.80
|
|
**/
|
|
gpointer
|
|
g_pointer_bit_lock_mask_ptr (gpointer ptr, guint lock_bit, gboolean set, guintptr preserve_mask, gpointer preserve_ptr)
|
|
{
|
|
g_return_val_if_fail (lock_bit < 32u || lock_bit == G_MAXUINT, ptr);
|
|
|
|
return pointer_bit_lock_mask_ptr (ptr, lock_bit, set, preserve_mask, preserve_ptr);
|
|
}
|
|
|
|
/**
|
|
* g_pointer_bit_unlock_and_set:
|
|
* @address: (not nullable): a pointer to a #gpointer-sized value
|
|
* @lock_bit: a bit value between 0 and 31
|
|
* @ptr: the new pointer value to set
|
|
* @preserve_mask: if non-zero, those bits of the current pointer in @address
|
|
* are preserved.
|
|
* Note that the @lock_bit bit will be always set according to @set,
|
|
* regardless of @preserve_mask and the currently set value in @address.
|
|
*
|
|
* This is equivalent to g_pointer_bit_unlock() and atomically setting
|
|
* the pointer value.
|
|
*
|
|
* Note that the lock bit will be cleared from the pointer. If the unlocked
|
|
* pointer that was set is not identical to @ptr, an assertion fails. In other
|
|
* words, @ptr must have @lock_bit unset. This also means, you usually can
|
|
* only use this on the lowest bits.
|
|
*
|
|
* Since: 2.80
|
|
**/
|
|
void (g_pointer_bit_unlock_and_set) (void *address,
|
|
guint lock_bit,
|
|
gpointer ptr,
|
|
guintptr preserve_mask)
|
|
{
|
|
gpointer *pointer_address = address;
|
|
guint class = bit_lock_contended_class (address);
|
|
gpointer ptr2;
|
|
|
|
g_return_if_fail (lock_bit < 32u);
|
|
|
|
if (preserve_mask != 0)
|
|
{
|
|
gpointer old_ptr = g_atomic_pointer_get ((gpointer *) address);
|
|
|
|
again:
|
|
ptr2 = pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, preserve_mask, old_ptr);
|
|
if (!g_atomic_pointer_compare_and_exchange_full (pointer_address, old_ptr, ptr2, &old_ptr))
|
|
goto again;
|
|
}
|
|
else
|
|
{
|
|
ptr2 = pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, 0, NULL);
|
|
g_atomic_pointer_set (pointer_address, ptr2);
|
|
}
|
|
|
|
if (g_atomic_int_get (&g_bit_lock_contended[class]) > 0)
|
|
g_futex_wake (g_futex_int_address (address));
|
|
|
|
/* It makes no sense, if unlocking mangles the pointer. Assert against
|
|
* that.
|
|
*
|
|
* Note that based on @preserve_mask, the pointer also gets mangled, which
|
|
* can make sense for the caller. We don't assert for that. */
|
|
g_return_if_fail (ptr == pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, 0, NULL));
|
|
}
|