[glib/wip/refptr: 1/5] Add reference counted memory areas



commit c7cac3bc408d36f8cdf4fa5dc32c0dd7cf3bf453
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Mon Jan 26 12:26:38 2015 +0000

    Add reference counted memory areas
    
    A lot of our data structures are reference counted these days. It can be
    useful to allow creating reference counted data without necessarily
    duplicating all the reference counting machinery — a field in the
    struct, reference and unreference functions, a GDestroyNotify-compatible
    function for destroying the contents.
    
    We can re-use the same overallocation and pointer offset trick we use
    when allocating a GTypeInstance in gobject, and add all the reference
    counting meta-information directly on top of the allocation size.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=622721

 docs/reference/glib/glib-docs.xml     |    1 +
 docs/reference/glib/glib-sections.txt |   24 ++
 glib/Makefile.am                      |    2 +
 glib/glib.h                           |    1 +
 glib/gref.c                           |  464 +++++++++++++++++++++++++++++++++
 glib/gref.h                           |   65 +++++
 6 files changed, 557 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml
index 12de3f2..cfec541 100644
--- a/docs/reference/glib/glib-docs.xml
+++ b/docs/reference/glib/glib-docs.xml
@@ -55,6 +55,7 @@
     <xi:include href="xml/modules.xml" />
     <xi:include href="xml/memory.xml" />
     <xi:include href="xml/memory_slices.xml" />
+    <xi:include href="xml/refcount.xml" />
     <xi:include href="xml/iochannels.xml" />
     <xi:include href="xml/error_reporting.xml" />
     <xi:include href="xml/warnings.xml" />
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 45827d6..b6b6f35 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1017,6 +1017,30 @@ g_mem_profile
 </SECTION>
 
 <SECTION>
+<FILE>refcount</FILE>
+g_ref_pointer_new
+g_ref_pointer_new0
+
+<SUBSECTION>
+g_ref_pointer_alloc
+g_ref_pointer_alloc0
+g_ref_pointer_take
+
+<SUBSECTION>
+g_ref_pointer_make_atomic
+
+<SUBSECTION>
+g_ref_pointer_acquire
+g_ref_pointer_release
+
+<SUBSECTION>
+g_ref_count_init
+g_ref_count_inc
+g_ref_count_dec
+g_ref_count_make_atomic
+</SECTION>
+
+<SECTION>
 <TITLE>Warnings and Assertions</TITLE>
 <FILE>warnings</FILE>
 g_print
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 9c728f8..59c5d93 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -153,6 +153,7 @@ libglib_2_0_la_SOURCES =    \
        gquark.c                \
        gqueue.c                \
        grand.c                 \
+       gref.c                  \
        gregex.c                \
        gscanner.c              \
        gscripttable.h          \
@@ -285,6 +286,7 @@ glibsubinclude_HEADERS = \
        gquark.h        \
        gqueue.h        \
        grand.h         \
+       gref.h          \
        gregex.h        \
        gscanner.h      \
        gsequence.h     \
diff --git a/glib/glib.h b/glib/glib.h
index 1212d13..0d60063 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -69,6 +69,7 @@
 #include <glib/gquark.h>
 #include <glib/gqueue.h>
 #include <glib/grand.h>
+#include <glib/gref.h>
 #include <glib/gregex.h>
 #include <glib/gscanner.h>
 #include <glib/gsequence.h>
diff --git a/glib/gref.c b/glib/gref.c
new file mode 100644
index 0000000..93e3da3
--- /dev/null
+++ b/glib/gref.c
@@ -0,0 +1,464 @@
+/* gref.h: Reference counted memory areas
+ *
+ * Copyright 2015  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 of the licence, 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/>.
+ */
+
+/**
+ * SECTION:refcount
+ * @short_description: reference counted memory areas
+ * @title: References
+ *
+ * These functions provide support for allocating and freeing reference
+ * counted memory areas.
+ *
+ * Reference counted memory areas are kept alive as long as something holds
+ * a reference on them; as soon as their reference count drops to zero, the
+ * associated memory is freed.
+ */
+
+#include "config.h"
+#include "glibconfig.h"
+
+#include "gref.h"
+
+#include "gatomic.h"
+#include "gconstructor.h"
+#include "gmessages.h"
+#include "gtestutils.h"
+#include "valgrind.h"
+
+#include <string.h>
+
+/**
+ * g_ref_count_init:
+ * @refcount: a pointer to an integer used for reference counting
+ * @atomic: whether reference counting should be atomic
+ *
+ * Initializes a reference counting value.
+ *
+ * Since: 2.44
+ */
+void
+g_ref_count_init (volatile int *refcount,
+                  gboolean      atomic)
+{
+  *refcount = atomic ? -1 : 1;
+}
+
+/**
+ * g_ref_count_inc:
+ * @refcount: a pointer to an integer used for reference counting
+ *
+ * Increases a reference counter, using atomic operations if needed.
+ *
+ * Since: 2.44
+ */
+void
+g_ref_count_inc (volatile int *refcount)
+{
+  int refs;
+
+test_again:
+  refs = *refcount;
+
+  if (refs > 0)
+    *refcount += 1;
+  else if (G_UNLIKELY (!g_atomic_int_compare_and_exchange (refcount, refs, refs - 1)))
+    goto test_again;
+}
+
+/**
+ * g_ref_count_dec:
+ * @refcount: a pointer to an integer used for reference counting
+ *
+ * Decreases a reference counter, using atomic operations if needed.
+ *
+ * Returns: %TRUE if the reference counter hit zero
+ *
+ * Since: 2.44
+ */
+gboolean
+g_ref_count_dec (volatile int *refcount)
+{
+  int refs;
+
+test_again:
+  refs = *refcount;
+  if (refs == -1 || refs == 1)
+    return TRUE;
+  if (refs > 0)
+    *refcount -= 1;
+  else if (G_UNLIKELY (!g_atomic_int_compare_and_exchange (refcount, refs, refs + 1)))
+    goto test_again;
+
+  return FALSE;
+}
+
+/**
+ * g_ref_count_make_atomic:
+ * @refcount: a pointer to an integer used for reference counting
+ *
+ * Changes the reference counting semantics of a reference counter to
+ * be atomic if they weren't.
+ *
+ * Since: 2.44
+ */
+void
+g_ref_count_make_atomic (volatile int *refcount)
+{
+  int refs = g_atomic_int_get (refcount);
+
+  if (refs > 0)
+    *refcount = -refs;
+}
+
+#define STRUCT_ALIGNMENT        (2 * sizeof (gsize))
+#define ALIGN_STRUCT(offset)    ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
+
+#define G_REF_PTR_SIZE          sizeof (GRefPtr)
+#define G_REF_PTR(ptr)          (GRefPtr *) (((char *) (ptr)) - G_REF_PTR_SIZE)
+
+typedef struct _GRefPtr         GRefPtr;
+
+struct _GRefPtr
+{
+  volatile int ref_count;
+
+  gsize alloc_size;
+
+  GDestroyNotify notify;
+};
+
+#ifdef G_ENABLE_DEBUG
+#include "ghash.h"
+#include "gthread.h"
+
+static GHashTable *referenced_pointers;
+G_LOCK_DEFINE_STATIC (referenced_pointers);
+
+static inline void
+g_ref_pointer_register (gpointer ref)
+{
+  G_LOCK (referenced_pointers);
+  if (G_UNLIKELY (referenced_pointers == NULL))
+    referenced_pointers = g_hash_table_new (NULL, NULL);
+
+  g_hash_table_add (referenced_pointers, ref);
+  G_UNLOCK (referenced_pointers);
+}
+
+static inline void
+g_ref_pointer_unregister (gpointer ref)
+{
+  G_LOCK (referenced_pointers);
+  if (G_LIKELY (referenced_pointers != NULL))
+    g_hash_table_remove (referenced_pointers, ref);
+  G_UNLOCK (referenced_pointers);
+}
+
+static gboolean
+g_is_ref_pointer (gconstpointer ref)
+{
+  gboolean res = FALSE;
+
+  G_LOCK (referenced_pointers);
+  if (G_LIKELY (referenced_pointers != NULL))
+    res = g_hash_table_contains (referenced_pointers, ref);
+  G_UNLOCK (referenced_pointers);
+
+  return res;
+}
+
+#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
+#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(glib_ref_pointers_clear)
+#endif
+G_DEFINE_DESTRUCTOR(glib_ref_pointers_clear)
+
+static void
+glib_ref_pointers_clear (void)
+{
+  if (referenced_pointers != NULL)
+    g_hash_table_unref (referenced_pointers);
+}
+
+#endif /* G_ENABLE_DEBUG */
+
+/**
+ * g_ref_pointer_new:
+ * @Type: the type to allocate
+ * @free_func: the free function
+ *
+ * Allocates a reference counted memory area corresponding to @Type.
+ *
+ * See also: g_ref_pointer_alloc()
+ *
+ * Returns: the newly allocated memory area
+ *
+ * Since: 2.44
+ */
+
+/**
+ * g_ref_pointer_new0:
+ * @Type: the type to allocate
+ * @free_func: the free function
+ *
+ * Allocates and clears a reference counted memory area corresponding
+ * to @Type.
+ *
+ * See also: g_ref_pointer_alloc0()
+ *
+ * Returns: the newly allocated memory area
+ *
+ * Since: 2.44
+ */
+
+/*< private >
+ * g_ref_pointer_destroy:
+ * @ref: a reference counted memory area
+ *
+ * Forces the destruction of a reference counter memory area.
+ */
+static void
+g_ref_pointer_destroy (gpointer ref)
+{
+  GRefPtr *real = G_REF_PTR (ref);
+  gsize alloc_size = real->alloc_size;
+  gsize private_size = G_REF_PTR_SIZE;
+  char *allocated = ((char *) ref) - private_size;
+
+  if (real->notify != NULL)
+    real->notify (ref);
+
+#ifdef G_ENABLE_DEBUG
+  g_ref_pointer_unregister (ref);
+#endif
+
+  if (RUNNING_ON_VALGRIND)
+    {
+      private_size += ALIGN_STRUCT (1);
+      allocated -= ALIGN_STRUCT (1);
+
+      *(gpointer *) (allocated + private_size + alloc_size) = NULL;
+      g_free (allocated);
+    }
+  else
+    g_free (allocated);
+}
+
+static gpointer
+g_ref_pointer_alloc_internal (gsize          alloc_size,
+                              gboolean       clear,
+                              GDestroyNotify notify)
+{
+  gsize private_size = G_REF_PTR_SIZE;
+  gchar *allocated;
+  GRefPtr *real;
+
+  g_return_val_if_fail (alloc_size != 0, NULL);
+
+  if (RUNNING_ON_VALGRIND)
+    {
+      private_size += ALIGN_STRUCT (1);
+
+      if (clear)
+        allocated = g_malloc0 (private_size + alloc_size + sizeof (gpointer));
+      else
+        allocated = g_malloc (private_size + alloc_size + sizeof (gpointer));
+
+      *(gpointer *) (allocated + private_size + alloc_size) = allocated + ALIGN_STRUCT (1);
+
+      VALGRIND_MALLOCLIKE_BLOCK (allocated + private_size, alloc_size + sizeof (gpointer), 0, TRUE);
+      VALGRIND_MALLOCLIKE_BLOCK (allocated + ALIGN_STRUCT (1), private_size - ALIGN_STRUCT (1), 0, TRUE);
+    }
+  else
+    {
+      if (clear)
+        allocated = g_malloc0 (private_size + alloc_size);
+      else
+        allocated = g_malloc (private_size + alloc_size);
+    }
+
+#ifdef G_ENABLE_DEBUG
+  g_ref_pointer_register (allocated + private_size);
+#endif
+
+  real = (GRefPtr *) allocated;
+  real->notify = notify;
+  real->alloc_size = alloc_size;
+  g_ref_count_init (&real->ref_count, FALSE);
+
+  return allocated + private_size;
+}
+
+/**
+ * g_ref_pointer_alloc:
+ * @alloc_size: the number of bytes to allocate
+ * @notify: (nullable): a function to be called when the reference
+ *   count drops to zero
+ *
+ * Allocates a reference counted memory area.
+ *
+ * Reference counted memory areas are automatically freed when their
+ * reference count drops to zero.
+ *
+ * Use g_ref_pointer_acquire() to acquire a reference, and
+ * g_ref_pointer_release() to release it when you're done.
+ *
+ * The contents of the returned memory are undefined.
+ *
+ * Returns: a pointer to the allocated memory
+ *
+ * Since: 2.44
+ */
+gpointer
+g_ref_pointer_alloc (gsize          alloc_size,
+                     GDestroyNotify notify)
+{
+  return g_ref_pointer_alloc_internal (alloc_size, FALSE, notify);
+}
+
+/**
+ * g_ref_pointer_alloc0:
+ * @alloc_size: the number of bytes to allocate
+ * @notify: (nullable): a function to be called when the reference
+ *   count drops to zero
+ *
+ * Allocates a reference counted memory area.
+ *
+ * Reference counted memory areas are automatically freed when their
+ * reference count drops to zero.
+ *
+ * Use g_ref_pointer_acquire() to acquire a reference, and
+ * g_ref_pointer_release() to release it.
+ *
+ * The contents of the returned memory are set to 0.
+ *
+ * Returns: a pointer to the allocated memory
+ *
+ * Since: 2.44
+ */
+gpointer
+g_ref_pointer_alloc0 (gsize          alloc_size,
+                      GDestroyNotify notify)
+{
+  return g_ref_pointer_alloc_internal (alloc_size, TRUE, notify);
+}
+
+/**
+ * g_ref_pointer_take:
+ * @data: data to duplicate
+ * @alloc_size: the size of @data
+ * @notify: notification function to be called when the reference
+ *   count drops to zero
+ *
+ * Duplicates existing data into a reference counted memory area.
+ *
+ * Returns: the newly allocated reference counted area
+ *
+ * Since: 2.44
+ */
+gpointer
+g_ref_pointer_take (gconstpointer  data,
+                    gsize          alloc_size,
+                    GDestroyNotify notify)
+{
+  gpointer res = g_ref_pointer_alloc (alloc_size, notify);
+
+  memcpy (res, data, alloc_size);
+
+  return res;
+}
+
+/**
+ * g_ref_pointer_acquire:
+ * @ref: a reference counted memory area
+ *
+ * Acquires a reference on the given memory area.
+ *
+ * Use g_ref_pointer_release() to release the reference when done.
+ *
+ * You should only call this function if you are implementing
+ * a reference counted type.
+ *
+ * Returns: the memory area, with its reference count increased by 1
+ *
+ * Since: 2.44
+ */
+gpointer
+g_ref_pointer_acquire (gpointer ref)
+{
+  GRefPtr *real = G_REF_PTR (ref);
+
+  g_return_val_if_fail (ref != NULL, NULL);
+#ifdef G_ENABLE_DEBUG
+  g_return_val_if_fail (g_is_ref_pointer (ref), ref);
+#endif
+
+  g_ref_count_inc (&real->ref_count);
+
+  return ref;
+}
+
+/**
+ * g_ref_pointer_release:
+ * @ref: a reference counted memory area
+ *
+ * Releases a reference acquired using g_ref_pointer_acquire().
+ *
+ * If the reference count drops to zero, the notification function
+ * used when allocating the memory will be called, and then the memory
+ * area will be freed.
+ *
+ * You should only call this function if you are implementing
+ * a reference counted type.
+ *
+ * Since: 2.44
+ */
+void
+g_ref_pointer_release (gpointer ref)
+{
+  GRefPtr *real = G_REF_PTR (ref);
+
+  g_return_if_fail (ref != NULL);
+#ifdef G_ENABLE_DEBUG
+  g_return_if_fail (g_is_ref_pointer (ref));
+#endif
+
+  if (g_ref_count_dec (&real->ref_count))
+    g_ref_pointer_destroy (ref);
+}
+
+/**
+ * g_ref_pointer_make_atomic:
+ * @ref: a reference counted memory area
+ *
+ * Makes reference count operations on a reference counted memory area
+ * always atomic.
+ *
+ * Since: 2.44
+ */
+void
+g_ref_pointer_make_atomic (gpointer ref)
+{
+  GRefPtr *real = G_REF_PTR (ref);
+
+  g_return_if_fail (ref != NULL);
+#ifdef G_ENABLE_DEBUG
+  g_return_if_fail (g_is_ref_pointer (ref));
+#endif
+
+  g_ref_count_make_atomic (&real->ref_count);
+}
diff --git a/glib/gref.h b/glib/gref.h
new file mode 100644
index 0000000..dbbd2d2
--- /dev/null
+++ b/glib/gref.h
@@ -0,0 +1,65 @@
+/* gref.h: Reference counted memory areas
+ *
+ * Copyright 2015  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 of the licence, 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/>.
+ */
+
+#ifndef __G_REF_H__
+#define __G_REF_H__
+
+#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+GLIB_AVAILABLE_IN_2_44
+gpointer        g_ref_pointer_alloc             (gsize          alloc_size,
+                                                 GDestroyNotify notify) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE (1);
+GLIB_AVAILABLE_IN_2_44
+gpointer        g_ref_pointer_alloc0            (gsize          alloc_size,
+                                                 GDestroyNotify notify) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE (1);
+
+#define g_ref_pointer_new(Type,free_func)       ((Type *) g_ref_pointer_alloc (sizeof (Type), 
(GDestroyNotify) free_func))
+
+#define g_ref_pointer_new0(Type,free_func)      ((Type *) g_ref_pointer_alloc0 (sizeof (Type), 
(GDestroyNotify) free_func))
+
+GLIB_AVAILABLE_IN_2_44
+gpointer        g_ref_pointer_take              (gconstpointer  data,
+                                                 gsize          alloc_size,
+                                                 GDestroyNotify notify) G_GNUC_WARN_UNUSED_RESULT;
+GLIB_AVAILABLE_IN_2_44
+void            g_ref_pointer_make_atomic       (gpointer       ref);
+
+GLIB_AVAILABLE_IN_2_44
+gpointer        g_ref_pointer_acquire           (gpointer       ref);
+GLIB_AVAILABLE_IN_2_44
+void            g_ref_pointer_release           (gpointer       ref);
+
+GLIB_AVAILABLE_IN_2_44
+void            g_ref_count_init        (volatile int  *refcount,
+                                         gboolean       atomic);
+GLIB_AVAILABLE_IN_2_44
+void            g_ref_count_inc         (volatile int  *refcount);
+GLIB_AVAILABLE_IN_2_44
+gboolean        g_ref_count_dec         (volatile int  *refcount);
+GLIB_AVAILABLE_IN_2_44
+void            g_ref_count_make_atomic (volatile int  *refcount);
+
+G_END_DECLS
+
+#endif /* __G_REF_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]