mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-05-16 18:51:58 +02:00
Implement pointer sized bitlocks
Based on a patch from Alexander Larsson. https://bugzilla.gnome.org/show_bug.cgi?id=651467
This commit is contained in:
parent
1a80405a36
commit
df0b208831
193
glib/gbitlock.c
193
glib/gbitlock.c
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "gbitlock.h"
|
#include "gbitlock.h"
|
||||||
|
|
||||||
|
#include <glib/gmessages.h>
|
||||||
#include <glib/gatomic.h>
|
#include <glib/gatomic.h>
|
||||||
#include <glib/gslist.h>
|
#include <glib/gslist.h>
|
||||||
#include <glib/gthread.h>
|
#include <glib/gthread.h>
|
||||||
@ -334,3 +335,195 @@ g_bit_unlock (volatile gint *address,
|
|||||||
g_futex_wake (address);
|
g_futex_wake (address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 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 volatile gint *
|
||||||
|
g_futex_int_address (const volatile void *address)
|
||||||
|
{
|
||||||
|
const volatile gint *int_address = address;
|
||||||
|
|
||||||
|
#if G_BYTE_ORDER == G_BIG_ENDIAN && GLIB_SIZEOF_VOID_P == 8
|
||||||
|
int_address++;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return int_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g_pointer_bit_lock:
|
||||||
|
* @address: 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.
|
||||||
|
*
|
||||||
|
* Since: 2.30
|
||||||
|
**/
|
||||||
|
void
|
||||||
|
(g_pointer_bit_lock) (volatile void *address,
|
||||||
|
gint lock_bit)
|
||||||
|
{
|
||||||
|
g_return_if_fail (lock_bit < 32);
|
||||||
|
|
||||||
|
{
|
||||||
|
#if defined (__GNUC__) && (defined (i386) || defined (__amd64__))
|
||||||
|
retry:
|
||||||
|
asm volatile goto ("lock bts %1, (%0)\n"
|
||||||
|
"jc %l[contended]"
|
||||||
|
: /* no output */
|
||||||
|
: "r" (address), "r" ((gsize) lock_bit)
|
||||||
|
: "cc", "memory"
|
||||||
|
: contended);
|
||||||
|
return;
|
||||||
|
|
||||||
|
contended:
|
||||||
|
{
|
||||||
|
volatile gsize *pointer_address = address;
|
||||||
|
gsize mask = 1u << lock_bit;
|
||||||
|
gsize v;
|
||||||
|
|
||||||
|
v = (gsize) g_atomic_pointer_get (pointer_address);
|
||||||
|
if (v & mask)
|
||||||
|
{
|
||||||
|
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto retry;
|
||||||
|
#else
|
||||||
|
volatile gsize *pointer_address = address;
|
||||||
|
gsize mask = 1u << lock_bit;
|
||||||
|
gsize v;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
v = g_atomic_pointer_or (pointer_address, mask);
|
||||||
|
if (v & mask)
|
||||||
|
/* already locked */
|
||||||
|
{
|
||||||
|
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g_pointer_bit_trylock:
|
||||||
|
* @address: a pointer to a #gpointer-sized value
|
||||||
|
* @lock_bit: a bit value between 0 and 31
|
||||||
|
* @returns: %TRUE if the lock was acquired
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Since: 2.30
|
||||||
|
**/
|
||||||
|
gboolean
|
||||||
|
(g_pointer_bit_trylock) (volatile void *address,
|
||||||
|
gint lock_bit)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (lock_bit < 32, FALSE);
|
||||||
|
|
||||||
|
{
|
||||||
|
#if defined (__GNUC__) && (defined (i386) || defined (__amd64__))
|
||||||
|
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
|
||||||
|
volatile gsize *pointer_address = address;
|
||||||
|
gsize mask = 1u << lock_bit;
|
||||||
|
gsize v;
|
||||||
|
|
||||||
|
g_return_val_if_fail (lock_bit < 32, FALSE);
|
||||||
|
|
||||||
|
v = g_atomic_pointer_or (pointer_address, mask);
|
||||||
|
|
||||||
|
return ~v & mask;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g_pointer_bit_unlock:
|
||||||
|
* @address: 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.
|
||||||
|
*
|
||||||
|
* Since: 2.30
|
||||||
|
**/
|
||||||
|
void
|
||||||
|
(g_pointer_bit_unlock) (volatile void *address,
|
||||||
|
gint lock_bit)
|
||||||
|
{
|
||||||
|
g_return_if_fail (lock_bit < 32);
|
||||||
|
|
||||||
|
{
|
||||||
|
#if defined (__GNUC__) && (defined (i386) || defined (__amd64__))
|
||||||
|
asm volatile ("lock btr %1, (%0)"
|
||||||
|
: /* no output */
|
||||||
|
: "r" (address), "r" ((gsize) lock_bit)
|
||||||
|
: "cc", "memory");
|
||||||
|
#else
|
||||||
|
volatile gsize *pointer_address = address;
|
||||||
|
gsize mask = 1u << lock_bit;
|
||||||
|
|
||||||
|
g_atomic_pointer_and (pointer_address, ~mask);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
guint class = ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
|
||||||
|
if (g_atomic_int_get (&g_bit_lock_contended[class]))
|
||||||
|
g_futex_wake (g_futex_int_address (address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -38,6 +38,35 @@ gboolean g_bit_trylock (volatile gint *address,
|
|||||||
void g_bit_unlock (volatile gint *address,
|
void g_bit_unlock (volatile gint *address,
|
||||||
gint lock_bit);
|
gint lock_bit);
|
||||||
|
|
||||||
|
void g_pointer_bit_lock (volatile void *address,
|
||||||
|
gint lock_bit);
|
||||||
|
gboolean g_pointer_bit_trylock (volatile void *address,
|
||||||
|
gint lock_bit);
|
||||||
|
void g_pointer_bit_unlock (volatile void *address,
|
||||||
|
gint lock_bit);
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
|
||||||
|
#define g_pointer_bit_lock(address, lock_bit) \
|
||||||
|
(G_GNUC_EXTENSION ({ \
|
||||||
|
G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \
|
||||||
|
g_pointer_bit_lock ((address), (lock_bit)); \
|
||||||
|
}))
|
||||||
|
|
||||||
|
#define g_pointer_bit_trylock(address, lock_bit) \
|
||||||
|
(G_GNUC_EXTENSION ({ \
|
||||||
|
G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \
|
||||||
|
g_pointer_bit_trylock ((address), (lock_bit)); \
|
||||||
|
}))
|
||||||
|
|
||||||
|
#define g_pointer_bit_unlock(address, lock_bit) \
|
||||||
|
(G_GNUC_EXTENSION ({ \
|
||||||
|
G_STATIC_ASSERT (sizeof *(address) == sizeof (gpointer)); \
|
||||||
|
g_pointer_bit_unlock ((address), (lock_bit)); \
|
||||||
|
}))
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __G_BITLOCK_H_ */
|
#endif /* __G_BITLOCK_H_ */
|
||||||
|
@ -1065,6 +1065,9 @@ g_string_append_c
|
|||||||
g_bit_lock
|
g_bit_lock
|
||||||
g_bit_trylock
|
g_bit_trylock
|
||||||
g_bit_unlock
|
g_bit_unlock
|
||||||
|
g_pointer_bit_lock
|
||||||
|
g_pointer_bit_trylock
|
||||||
|
g_pointer_bit_unlock
|
||||||
g_once_impl
|
g_once_impl
|
||||||
g_once_init_enter_impl
|
g_once_init_enter_impl
|
||||||
g_once_init_leave
|
g_once_init_leave
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#define ITERATIONS 10000
|
#define ITERATIONS 10000
|
||||||
#define THREADS 100
|
#define THREADS 100
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
#if TEST_EMULATED_FUTEX
|
#if TEST_EMULATED_FUTEX
|
||||||
/* this is defined for the 1bit-mutex-emufutex test.
|
/* this is defined for the 1bit-mutex-emufutex test.
|
||||||
@ -31,9 +32,16 @@
|
|||||||
/* rebuild gbitlock.c without futex support,
|
/* rebuild gbitlock.c without futex support,
|
||||||
defining our own version of the g_bit_*lock symbols
|
defining our own version of the g_bit_*lock symbols
|
||||||
*/
|
*/
|
||||||
|
#undef g_pointer_bit_lock
|
||||||
|
#undef g_pointer_bit_trylock
|
||||||
|
#undef g_pointer_bit_unlock
|
||||||
|
|
||||||
#define g_bit_lock _emufutex_g_bit_lock
|
#define g_bit_lock _emufutex_g_bit_lock
|
||||||
#define g_bit_trylock _emufutex_g_bit_trylock
|
#define g_bit_trylock _emufutex_g_bit_trylock
|
||||||
#define g_bit_unlock _emufutex_g_bit_unlock
|
#define g_bit_unlock _emufutex_g_bit_unlock
|
||||||
|
#define g_pointer_bit_lock _emufutex_g_pointer_bit_lock
|
||||||
|
#define g_pointer_bit_trylock _emufutex_g_pointer_bit_trylock
|
||||||
|
#define g_pointer_bit_unlock _emufutex_g_pointer_bit_unlock
|
||||||
#define _g_futex_thread_init _emufutex_g_futex_thread_init
|
#define _g_futex_thread_init _emufutex_g_futex_thread_init
|
||||||
|
|
||||||
#define G_BIT_LOCK_FORCE_FUTEX_EMULATION
|
#define G_BIT_LOCK_FORCE_FUTEX_EMULATION
|
||||||
@ -41,24 +49,32 @@
|
|||||||
#include <glib/gbitlock.c>
|
#include <glib/gbitlock.c>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
volatile GThread *owners[LOCKS];
|
volatile GThread *owners[LOCKS];
|
||||||
volatile gint locks[LOCKS];
|
volatile gint locks[LOCKS];
|
||||||
|
volatile gpointer ptrs[LOCKS];
|
||||||
volatile gint bits[LOCKS];
|
volatile gint bits[LOCKS];
|
||||||
|
|
||||||
static void
|
static void
|
||||||
acquire (int nr)
|
acquire (int nr,
|
||||||
|
gboolean use_pointers)
|
||||||
{
|
{
|
||||||
GThread *self;
|
GThread *self;
|
||||||
|
|
||||||
self = g_thread_self ();
|
self = g_thread_self ();
|
||||||
|
|
||||||
if (!g_bit_trylock (&locks[nr], bits[nr]))
|
g_assert_cmpint (((gsize) ptrs) % 8, ==, 0);
|
||||||
|
|
||||||
|
if (!(use_pointers ?
|
||||||
|
g_pointer_bit_trylock (&ptrs[nr], bits[nr])
|
||||||
|
: g_bit_trylock (&locks[nr], bits[nr])))
|
||||||
{
|
{
|
||||||
if (g_test_verbose ())
|
if (g_test_verbose ())
|
||||||
g_print ("thread %p going to block on lock %d\n", self, nr);
|
g_print ("thread %p going to block on lock %d\n", self, nr);
|
||||||
g_bit_lock (&locks[nr], bits[nr]);
|
|
||||||
|
if (use_pointers)
|
||||||
|
g_pointer_bit_lock (&ptrs[nr], bits[nr]);
|
||||||
|
else
|
||||||
|
g_bit_lock (&locks[nr], bits[nr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_assert (owners[nr] == NULL); /* hopefully nobody else is here */
|
g_assert (owners[nr] == NULL); /* hopefully nobody else is here */
|
||||||
@ -71,23 +87,34 @@ acquire (int nr)
|
|||||||
|
|
||||||
g_assert (owners[nr] == self); /* hopefully this is still us... */
|
g_assert (owners[nr] == self); /* hopefully this is still us... */
|
||||||
owners[nr] = NULL; /* make way for the next guy */
|
owners[nr] = NULL; /* make way for the next guy */
|
||||||
g_bit_unlock (&locks[nr], bits[nr]);
|
|
||||||
|
if (use_pointers)
|
||||||
|
g_pointer_bit_unlock (&ptrs[nr], bits[nr]);
|
||||||
|
else
|
||||||
|
g_bit_unlock (&locks[nr], bits[nr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gpointer
|
static gpointer
|
||||||
thread_func (gpointer data)
|
thread_func (gpointer data)
|
||||||
{
|
{
|
||||||
|
gboolean use_pointers = GPOINTER_TO_INT (data);
|
||||||
gint i;
|
gint i;
|
||||||
|
GRand *rand;
|
||||||
|
|
||||||
|
rand = g_rand_new ();
|
||||||
|
|
||||||
for (i = 0; i < ITERATIONS; i++)
|
for (i = 0; i < ITERATIONS; i++)
|
||||||
acquire (g_random_int () % LOCKS);
|
acquire (g_rand_int_range (rand, 0, LOCKS), use_pointers);
|
||||||
|
|
||||||
|
g_rand_free (rand);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
testcase (void)
|
testcase (gconstpointer data)
|
||||||
{
|
{
|
||||||
|
gboolean use_pointers = GPOINTER_TO_INT (data);
|
||||||
GThread *threads[THREADS];
|
GThread *threads[THREADS];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -109,7 +136,9 @@ testcase (void)
|
|||||||
bits[i] = g_random_int () % 32;
|
bits[i] = g_random_int () % 32;
|
||||||
|
|
||||||
for (i = 0; i < THREADS; i++)
|
for (i = 0; i < THREADS; i++)
|
||||||
threads[i] = g_thread_create (thread_func, NULL, TRUE, NULL);
|
threads[i] = g_thread_create (thread_func,
|
||||||
|
GINT_TO_POINTER (use_pointers),
|
||||||
|
TRUE, NULL);
|
||||||
|
|
||||||
for (i = 0; i < THREADS; i++)
|
for (i = 0; i < THREADS; i++)
|
||||||
g_thread_join (threads[i]);
|
g_thread_join (threads[i]);
|
||||||
@ -126,7 +155,8 @@ main (int argc, char **argv)
|
|||||||
{
|
{
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
g_test_add_func ("/glib/1bit-mutex" SUFFIX, testcase);
|
g_test_add_data_func ("/glib/1bit-mutex" SUFFIX "/int", (gpointer) 0, testcase);
|
||||||
|
g_test_add_data_func ("/glib/1bit-mutex" SUFFIX "/pointer", (gpointer) 1, testcase);
|
||||||
|
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user