gthread: introduce g_once_init_{enter,leave}_pointer

These functions can be used to initalize pointer-type variables rather
than a gsize. This is required to support CHERI-enabled platforms where
gsize cannot be used to store pointers. Follow-up changes will migrate
the uses of g_once_init that store pointers to the new API

Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
This commit is contained in:
Alex Richardson 2023-09-11 23:20:23 -07:00 committed by Philip Withnall
parent 9c81ff46a2
commit 726eca7c89
3 changed files with 113 additions and 1 deletions

View File

@ -782,6 +782,8 @@ G_ONCE_INIT
g_once
g_once_init_enter
g_once_init_leave
g_once_init_enter_pointer
g_once_init_leave_pointer
<SUBSECTION>
g_bit_lock

View File

@ -87,7 +87,8 @@
* for condition variables to allow synchronization of threads (#GCond).
* There are primitives for thread-private data - data that every
* thread has a private instance of (#GPrivate). There are facilities
* for one-time initialization (#GOnce, g_once_init_enter()). Finally,
* for one-time initialization (#GOnce, g_once_init_enter_pointer(),
* g_once_init_enter()). Finally,
* there are primitives to create and manage threads (#GThread).
*
* The GLib threading system used to be initialized with g_thread_init().
@ -719,6 +720,54 @@ gboolean
return need_init;
}
/**
* g_once_init_enter_pointer:
* @location: (not nullable): location of a static initializable variable
* containing `NULL`
*
* This functions behaves in the same way as g_once_init_enter(), but can
* can be used to initialize pointers (or #guintptr) instead of #gsize.
*
* |[<!-- language="C" -->
* static MyStruct *interesting_struct = NULL;
*
* if (g_once_init_enter_pointer (&interesting_struct))
* {
* MyStruct *setup_value = allocate_my_struct (); // initialization code here
*
* g_once_init_leave_pointer (&interesting_struct, g_steal_pointer (&setup_value));
* }
*
* // use interesting_struct here
* ]|
*
* Returns: %TRUE if the initialization section should be entered,
* %FALSE and blocks otherwise
*
* Since: 2.80
*/
gboolean
(g_once_init_enter_pointer) (gpointer location)
{
gpointer *value_location = (gpointer *) location;
gboolean need_init = FALSE;
g_mutex_lock (&g_once_mutex);
if (g_atomic_pointer_get (value_location) == 0)
{
if (!g_slist_find (g_once_init_list, (void *) value_location))
{
need_init = TRUE;
g_once_init_list = g_slist_prepend (g_once_init_list, (void *) value_location);
}
else
do
g_cond_wait (&g_once_cond, &g_once_mutex);
while (g_slist_find (g_once_init_list, (void *) value_location));
}
g_mutex_unlock (&g_once_mutex);
return need_init;
}
/**
* g_once_init_leave:
* @location: (not nullable): location of a static initializable variable
@ -755,6 +804,42 @@ void
g_mutex_unlock (&g_once_mutex);
}
/**
* g_once_init_leave_pointer:
* @location: (not nullable): location of a static initializable variable
* containing `NULL`
* @result: new non-`NULL` value for `*location`
*
* Counterpart to g_once_init_enter_pointer(). Expects a location of a static
* `NULL`-initialized initialization variable, and an initialization value
* other than `NULL`. Sets the variable to the initialization value, and
* releases concurrent threads blocking in g_once_init_enter_pointer() on this
* initialization variable.
*
* This functions behaves in the same way as g_once_init_leave(), but
* can be used to initialize pointers (or #guintptr) instead of #gsize.
*
* Since: 2.80
*/
void
(g_once_init_leave_pointer) (gpointer location,
gpointer result)
{
gpointer *value_location = (gpointer *) location;
gpointer old_value;
g_return_if_fail (result != 0);
old_value = g_atomic_pointer_exchange (value_location, result);
g_return_if_fail (old_value == 0);
g_mutex_lock (&g_once_mutex);
g_return_if_fail (g_once_init_list != NULL);
g_once_init_list = g_slist_remove (g_once_init_list, (void *) value_location);
g_cond_broadcast (&g_once_cond);
g_mutex_unlock (&g_once_mutex);
}
/* GThread {{{1 -------------------------------------------------------- */
/**

View File

@ -236,6 +236,12 @@ GLIB_AVAILABLE_IN_ALL
void g_once_init_leave (volatile void *location,
gsize result);
GLIB_AVAILABLE_IN_2_80
gboolean g_once_init_enter_pointer (void *location);
GLIB_AVAILABLE_IN_2_80
void g_once_init_leave_pointer (void *location,
gpointer result);
/* Use C11-style atomic extensions to check the fast path for status=ready. If
* they are not available, fall back to using a mutex and condition variable in
* g_once_impl().
@ -268,11 +274,30 @@ void g_once_init_leave (volatile void *location,
0 ? (void) (*(location) = (result)) : (void) 0; \
g_once_init_leave ((location), (gsize) (result)); \
}))
# define g_once_init_enter_pointer(location) \
(G_GNUC_EXTENSION ({ \
G_STATIC_ASSERT (sizeof *(location) == sizeof (gpointer)); \
(void) (0 ? (gpointer) * (location) : NULL); \
(!g_atomic_pointer_get (location) && \
g_once_init_enter_pointer (location)); \
})) GLIB_AVAILABLE_MACRO_IN_2_80
# define g_once_init_leave_pointer(location, result) \
(G_GNUC_EXTENSION ({ \
G_STATIC_ASSERT (sizeof *(location) == sizeof (gpointer)); \
0 ? (void) (*(location) = (result)) : (void) 0; \
g_once_init_leave_pointer ((location), (gpointer) (guintptr) (result)); \
})) GLIB_AVAILABLE_MACRO_IN_2_80
#else
# define g_once_init_enter(location) \
(g_once_init_enter((location)))
# define g_once_init_leave(location, result) \
(g_once_init_leave((location), (gsize) (result)))
# define g_once_init_enter_pointer(location) \
(g_once_init_enter_pointer((location))) \
GLIB_AVAILABLE_MACRO_IN_2_80
# define g_once_init_leave_pointer(location, result) \
(g_once_init_leave_pointer((location), (gpointer) (guintptr) (result))) \
GLIB_AVAILABLE_MACRO_IN_2_80
#endif
GLIB_AVAILABLE_IN_2_36