diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml index 26cdafb67..d7ac0a500 100644 --- a/docs/reference/glib/glib-docs.xml +++ b/docs/reference/glib/glib-docs.xml @@ -120,6 +120,7 @@ + diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 01da779e8..fa83dd832 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3466,3 +3466,15 @@ g_atomic_ref_count_inc g_atomic_ref_count_dec g_atomic_ref_count_compare + +
+rcbox +g_rc_box_alloc +g_rc_box_alloc0 +g_rc_box_new +g_rc_box_new0 +g_rc_box_dup +g_rc_box_acquire +g_rc_box_release +g_rc_box_release_full +
diff --git a/glib/Makefile.am b/glib/Makefile.am index 7252a67d0..8ec5f4454 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -149,6 +149,7 @@ libglib_2_0_la_SOURCES = \ gquark.c \ gqueue.c \ grand.c \ + grcbox.c \ grefcount.c \ gregex.c \ gscanner.c \ @@ -286,6 +287,7 @@ glibsubinclude_HEADERS = \ gquark.h \ gqueue.h \ grand.h \ + grcbox.h \ grefcount.h \ gregex.h \ gscanner.h \ diff --git a/glib/glib.h b/glib/glib.h index 84299c4f9..309366281 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/grcbox.c b/glib/grcbox.c new file mode 100644 index 000000000..0860634be --- /dev/null +++ b/glib/grcbox.c @@ -0,0 +1,398 @@ +/* grcbox.h: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * 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 . + */ + +#include "config.h" + +#include "grcbox.h" + +#include "gmessages.h" +#include "grefcount.h" + +#ifdef ENABLE_VALGRIND +#include "valgrind.h" +#endif + +#include + +/** + * SECTION:rcbox + * @Title: Reference counted data + * @Short_description: Allocated memory with reference counting semantics + * + * A "reference counted box", or "RcBox", is an opaque wrapper data type + * that is guaranteed to be as big as the size of a given data type, and + * which augments the given data type with reference counting semantics + * for its memory management. + * + * RcBox is useful if you have a plain old data type, like a structure + * typically placed on the stack, and you wish to provide additional API + * to use it on the heap, without necessarily implementing copy/free + * semantics, or your own reference counting. + * + * The typical use is: + * + * |[ + * typedef struct { + * float x, y; + * } Point; + * + * Point * + * point_new (float x, float y) + * { + * Point *res = g_rc_box_new (Point); + * + * res->x = x; + * res->y = y; + * + * return res; + * } + * ]| + * + * Every time you wish to acquire a reference on the memory, you should + * call g_rc_box_acquire(); similarly, when you wish to release a reference + * you should call g_rc_box_release(): + * + * |[ + * Point * + * point_ref (Point *p) + * { + * return g_rc_box_acquire (p); + * } + * + * void + * point_unref (Point *p) + * { + * g_rc_box_release (p); + * } + * ]| + * + * If you have additional memory allocated inside the structure, you can + * use g_rc_box_release_full(), which takes a function pointer, which + * will be called if the reference released was the last: + * + * |[ + * typedef struct { + * char *name; + * char *address; + * char *city; + * char *state; + * int age; + * } Person; + * + * void + * person_clear (Person *p) + * { + * g_free (p->name); + * g_free (p->address); + * g_free (p->city); + * g_free (p->state); + * } + * + * void + * person_unref (Person *p) + * { + * g_rc_box_release_full (p, (GDestroyNotify) person_clear); + * } + * ]| + * + * If you wish to transfer the ownership of a reference counted data + * type without increasing the reference count, you can use g_steal_pointer(): + * + * |[ + * Person *p = g_rc_box_new (Person); + * + * fill_person_details (p); + * + * add_person_to_database (db, g_steal_pointer (&p)); + * ]| + * + * The reference counting operations on data allocated using g_rc_box_alloc(), + * g_rc_box_new(), and g_rc_box_dup() are not thread safe; it is your code's + * responsibility to ensure that references are acquired are released on the + * same thread. + * + * Since: 2.58. + */ + +typedef struct { + grefcount ref_count; + + gsize mem_size; + +#ifndef G_DISABLE_ASSERT + /* A "magic" number, used to perform additional integrity + * checks on the allocated data + */ + guint32 magic; +#endif +} GRcBox; + +#define G_RC_BOX_MAGIC 0x44ae2bf0 +#define G_RC_BOX_SIZE sizeof (GRcBox) +#define G_RC_BOX(p) (GRcBox *) (((char *) (p)) - G_RC_BOX_SIZE) + +/* We use the same alignment as GTypeInstance and GNU libc's malloc */ +#define STRUCT_ALIGNMENT (2 * sizeof (gsize)) +#define ALIGN_STRUCT(offset) ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT) + +static gpointer +g_rc_box_alloc_full (gsize block_size, + gboolean clear) +{ + gsize private_size = G_RC_BOX_SIZE; + gsize real_size = private_size + block_size; + char *allocated; + GRcBox *real_box; + +#ifdef ENABLE_VALGRIND + if (RUNNING_ON_VALGRIND) + { + /* When running under Valgrind we massage the memory allocation + * to include a pointer at the tail end of the block; the pointer + * is then set to the start of the block. This trick allows + * Valgrind to keep track of the over-allocation and not be + * confused when passing the pointer around + */ + private_size += ALIGN_STRUCT (1); + + if (clear) + allocated = g_malloc0 (real_size + sizeof (gpointer)); + else + allocated = g_malloc (real_size + sizeof (gpointer)); + + *(gpointer *) (allocated + private_size + block_size) = allocated + ALIGN_STRUCT (1); + + VALGRIND_MALLOCLIKE_BLOCK (allocated + private_size, block_size + sizeof (gpointer), 0, TRUE); + VALGRIND_MALLOCLIKE_BLOCK (allocated + ALIGN_STRUCT (1), private_size - ALIGN_STRUCT (1), 0, TRUE); + } + else +#endif /* ENABLE_VALGRIND */ + { + if (clear) + allocated = g_malloc0 (real_size); + else + allocated = g_malloc (real_size); + } + + real_box = (GRcBox *) allocated; + real_box->mem_size = block_size; +#ifndef G_DISABLE_ASSERT + real_box->magic = G_RC_BOX_MAGIC; +#endif + g_ref_count_init (&real_box->ref_count); + + return allocated + private_size; +} + +/** + * g_rc_box_alloc: + * @block_size: the size of the allocation + * + * Allocates @block_size bytes of memory, and adds reference + * counting semantics to it. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_rc_box_alloc (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, FALSE); +} + +/** + * g_rc_box_alloc0: + * @block_size: the size of the allocation + * + * Allocates @block_size bytes of memory, and adds reference + * counting semantics to it. + * + * The contents of the returned data is set to 0's. + * + * The data will be freed when its reference count drops to + * zero. + * + * Returns: a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +g_rc_box_alloc0 (gsize block_size) +{ + g_return_val_if_fail (block_size > 0, NULL); + + return g_rc_box_alloc_full (block_size, TRUE); +} + +/** + * g_rc_box_new: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate reference counted data with + * the size of the given @type. + * + * This macro calls g_rc_box_alloc() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * This macro cannot return %NULL, as the minimum allocation + * size from `sizeof (@type)` is 1 byte. + * + * Returns: (not nullable): a pointer to the allocated memory, + * cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_rc_box_new0: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate reference counted data with + * the size of the given @type, and set its contents to 0. + * + * This macro calls g_rc_box_alloc0() with `sizeof (@type)` and + * casts the returned pointer to a pointer of the given @type, + * avoiding a type cast in the source code. + * + * This macro cannot return %NULL, as the minimum allocation + * size from `sizeof (@type)` is 1 byte. + * + * Returns: (not nullable): a pointer to the allocated memory, + * cast to a pointer for the given @type + * + * Since: 2.58 + */ + +/** + * g_rc_box_dup: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Allocates a new block of data with reference counting + * semantics, and copies the contents of @mem_block into + * it. + * + * Returns: (not nullable): a pointer to the allocated memory + * + * Since: 2.58 + */ +gpointer +(g_rc_box_dup) (gpointer mem_block) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + gpointer res; + + g_return_val_if_fail (mem_block != NULL, NULL); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL); +#endif + + res = g_rc_box_alloc_full (real_box->mem_size, FALSE); + memcpy (res, mem_block, real_box->mem_size); + + return res; +} + +/** + * g_rc_box_acquire: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Acquires a reference on the data pointed by @mem_block. + * + * Returns: (not nullabl): a pointer to the data, with its reference + * count increased + * + * Since: 2.58 + */ +gpointer +(g_rc_box_acquire) (gpointer mem_block) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_val_if_fail (mem_block != NULL, NULL); +#ifndef G_DISABLE_ASSERT + g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL); +#endif + + g_ref_count_inc (&real_box->ref_count); + + return mem_block; +} + +/** + * g_rc_box_release: + * @mem_block: (not nullable): a pointer to reference counted data + * + * Releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_rc_box_release (gpointer mem_block) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_if_fail (mem_block != NULL); +#ifndef G_DISABLE_ASSERT + g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC); +#endif + + if (g_ref_count_dec (&real_box->ref_count)) + g_free (real_box); +} + +/** + * g_rc_box_release_full: + * @mem_block: (not nullabl): a pointer to reference counted data + * @clear_func: (not nullable): a function to call when clearing the data + * + * Releases a reference on the data pointed by @mem_block. + * + * If the reference was the last one, it will call @clear_func + * to clear the contents of @mem_block, and then will free the + * resources allocated for @mem_block. + * + * Since: 2.58 + */ +void +g_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func) +{ + GRcBox *real_box = G_RC_BOX (mem_block); + + g_return_if_fail (mem_block != NULL); + g_return_if_fail (clear_func != NULL); +#ifndef G_DISABLE_ASSERT + g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC); +#endif + + if (g_ref_count_dec (&real_box->ref_count)) + { + clear_func (mem_block); + g_free (real_box); + } +} diff --git a/glib/grcbox.h b/glib/grcbox.h new file mode 100644 index 000000000..8cd44037a --- /dev/null +++ b/glib/grcbox.h @@ -0,0 +1,55 @@ +/* grcbox.h: Reference counted data + * + * Copyright 2018 Emmanuele Bassi + * + * 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 . + */ + +#pragma once + +#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_alloc (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_alloc0 (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_dup (gpointer mem_block) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1); +GLIB_AVAILABLE_IN_2_58 +gpointer g_rc_box_acquire (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_rc_box_release (gpointer mem_block); +GLIB_AVAILABLE_IN_2_58 +void g_rc_box_release_full (gpointer mem_block, + GDestroyNotify clear_func); + +#define g_rc_box_new(type) \ + ((type *) g_rc_box_alloc (sizeof (type))) +#define g_rc_box_new0(type) \ + ((type *) g_rc_box_alloc0 (sizeof (type))) + +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && !defined(_cplusplus) +/* Type check to avoid assigning references to different types */ +# define g_rc_box_acquire(mem_block) ((__typeof__(mem_block)) (g_rc_box_acquire) (mem_block)) +/* Type check to avoid duplicating data to different types */ +# define g_rc_box_dup(mem_block) ((__typeof__(mem_block)) (g_rc_box_dup) (mem_block)) +#endif + +G_END_DECLS diff --git a/glib/meson.build b/glib/meson.build index 76d354c2a..73ab4928f 100644 --- a/glib/meson.build +++ b/glib/meson.build @@ -76,6 +76,7 @@ glib_sub_headers = files( 'gquark.h', 'gqueue.h', 'grand.h', + 'grcbox.h', 'grefcount.h', 'gregex.h', 'gscanner.h', @@ -160,6 +161,7 @@ glib_sources = files( 'gquark.c', 'gqueue.c', 'grand.c', + 'grcbox.c', 'grefcount.c', 'gregex.c', 'gscanner.c', diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am index 7289b419e..0d524d57f 100644 --- a/glib/tests/Makefile.am +++ b/glib/tests/Makefile.am @@ -89,6 +89,7 @@ test_programs = \ protocol \ queue \ rand \ + rcbox \ rec-mutex \ regex \ rwlock \ diff --git a/glib/tests/meson.build b/glib/tests/meson.build index cf05bc74f..3c20bad39 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -46,6 +46,7 @@ glib_tests = [ 'protocol', 'queue', 'rand', + 'rcbox', 'rec-mutex', 'refcount', 'refcount-macro', diff --git a/glib/tests/rcbox.c b/glib/tests/rcbox.c new file mode 100644 index 000000000..19b3842e5 --- /dev/null +++ b/glib/tests/rcbox.c @@ -0,0 +1,82 @@ +#include + +typedef struct { + float x, y; +} Point; + +static Point *global_point; + +static void +test_rcbox_new (void) +{ + Point *a = g_rc_box_new (Point); + + g_assert_nonnull (a); + + g_rc_box_release (a); + + a = g_rc_box_new0 (Point); + g_assert_nonnull (a); + g_assert_cmpfloat (a->x, ==, 0.f); + g_assert_cmpfloat (a->y, ==, 0.f); + + g_rc_box_release (a); +} + +static void +point_clear (Point *p) +{ + g_assert_nonnull (p); + + g_assert_cmpfloat (p->x, ==, 42.0f); + g_assert_cmpfloat (p->y, ==, 47.0f); + + g_assert_true (global_point == p); + global_point = NULL; +} + +static void +test_rcbox_release_full (void) +{ + Point *p = g_rc_box_new (Point); + + g_assert_nonnull (p); + global_point = p; + + p->x = 42.0f; + p->y = 47.0f; + + g_assert_true (g_rc_box_acquire (p) == p); + + g_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_nonnull (global_point); + g_assert_true (p == global_point); + + g_rc_box_release_full (p, (GDestroyNotify) point_clear); + g_assert_null (global_point); +} + +static void +test_rcbox_dup (void) +{ + Point *a = g_rc_box_new (Point); + Point *b = g_rc_box_dup (a); + + g_assert_true (a != b); + + g_rc_box_release (a); + g_rc_box_release (b); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/rcbox/new", test_rcbox_new); + g_test_add_func ("/rcbox/dup", test_rcbox_dup); + g_test_add_func ("/rcbox/release-full", test_rcbox_release_full); + + return g_test_run (); +}