diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index d5fa3fe99..f1939fddb 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -2524,6 +2524,20 @@ g_ptr_array_foreach
+
+List Arrays
+arraylist
+GArrayList
+g_array_list_new
+g_array_list_init
+g_array_list_peek
+g_array_list_index
+g_array_list_add
+g_array_list_remove
+g_array_list_remove_index
+g_array_list_destroy
+
+
Byte Arrays
arrays_byte
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 2783b51da..ba7003acb 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -103,6 +103,7 @@ libglib_2_0_la_SOURCES = \
$(deprecated_sources) \
glib_probes.d \
garray.c \
+ garraylist.c \
gasyncqueue.c \
gasyncqueueprivate.h \
gatomic.c \
@@ -245,6 +246,7 @@ glibsubinclude_HEADERS = \
glib-autocleanups.h \
galloca.h \
garray.h \
+ garraylist.h \
gasyncqueue.h \
gatomic.h \
gbacktrace.h \
diff --git a/glib/garraylist.c b/glib/garraylist.c
new file mode 100644
index 000000000..51086ff4a
--- /dev/null
+++ b/glib/garraylist.c
@@ -0,0 +1,428 @@
+/* garraylist.c
+ *
+ * Copyright (C) 2015 Christian Hergert
+ *
+ * This file 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 file 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 General Public License
+ * along with this program. If not, see .
+ */
+
+#include "config.h"
+
+#include
+
+#include "garraylist.h"
+#include "gmessages.h"
+#include "gslice.h"
+
+/**
+ * SECTION:garraylist
+ * @title: GArrayList
+ * @short_description: Linked lists implemented as arrays.
+ *
+ * Sometimes, when building APIs we make mistakes about the undelrying
+ * data structure that should have been used. GArrayList is a data structure
+ * that allows read-only compatability with #GList but is implemented as an
+ * array underneath. This means fast forward and backward iteration using
+ * typical array style index access, while providing read-only #GList access
+ * for compatability APIs.
+ *
+ * There is a cost associated with doing this, but the trade-off can be worth
+ * it under certain scenarios.
+ *
+ * Mutating the array list is potentially more expensive than a #GList.
+ * However, given no array growth is required, appending to the array list
+ * can be performed in O(1).
+ *
+ * The ideal use case for this data structure is a read-heavy data set
+ * where reverse iteration may be necessary and compatability with #GList
+ * must be maintained.
+ *
+ * It is unlikely that you should be writing new APIs that use this
+ * data structure.
+ *
+ * Since: 2.48
+ */
+
+#if 0
+# define DEBUG_ASSERT(x) g_assert(x)
+#else
+# define DEBUG_ASSERT(x)
+#endif
+
+#define G_ARRAY_LIST_DEFAULT_ALLOC 8
+
+/*
+ * The following structures give us a data-structure that can be dynamic,
+ * by changing it's implementation based on the number of items in the
+ * #GArrayList. When we have 2 or fewer items, we simply embed the #GList
+ * items in the toplevel structure.
+ *
+ * When this grows, we allocate an array to contain the #GList items and
+ * update prev/next pionters as necessary.
+ *
+ * This structure allows for fast iteration forwards and backwards from any
+ * arbitrary point in the structure, just like an array. It also allows for
+ * returning a read-only #GList for when API compatability is needed.
+ */
+
+typedef struct
+{
+ guint len;
+ guint flags;
+ GDestroyNotify destroy;
+ GList items[2];
+} GArrayListEmbed;
+
+typedef struct
+{
+ guint len;
+ guint flags;
+ GDestroyNotify destroy;
+
+ /*
+ * This is an array of GList items, which we keep in sync by updating
+ * the next/prev pointers when necessary.
+ */
+ GList *items;
+ gsize items_len;
+
+ gpointer padding[4];
+} GArrayListAlloc;
+
+typedef struct
+{
+ guint len;
+ guint on_heap : 1;
+ guint mode : 31;
+ GDestroyNotify destroy;
+ gpointer padding[6];
+} GArrayListAny;
+
+G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayList));
+G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListAlloc));
+G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListEmbed));
+
+enum {
+ MODE_EMBED,
+ MODE_ALLOC,
+};
+
+GArrayList *
+g_array_list_new (GDestroyNotify destroy)
+{
+ GArrayListAny *any;
+
+ any = g_slice_new0 (GArrayListAny);
+
+ any->mode = MODE_EMBED;
+ any->len = 0;
+ any->destroy = destroy;
+ any->on_heap = TRUE;
+
+ return (GArrayList *)any;
+}
+
+void
+g_array_list_init (GArrayList *self,
+ GDestroyNotify destroy)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+
+ g_return_if_fail (self != NULL);
+
+ memset (any, 0, sizeof *any);
+
+ any->mode = MODE_EMBED;
+ any->len = 0;
+ any->destroy = destroy;
+ any->on_heap = FALSE;
+}
+
+const GList *
+g_array_list_peek (GArrayList *self)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+
+ g_return_val_if_fail (self != NULL, NULL);
+
+ if (self->len == 0)
+ return NULL;
+
+ return (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items;
+}
+
+gpointer
+g_array_list_index (GArrayList *self,
+ guint index)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *items;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (index < self->len, NULL);
+
+ DEBUG_ASSERT ((any->len < G_N_ELEMENTS (embed->items)) ||
+ (any->mode == MODE_ALLOC));
+
+ items = (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items;
+
+ return items [index].data;
+}
+
+static void
+_g_array_list_grow (GArrayList *self)
+{
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ gpointer old_items;
+ gsize i;
+
+ DEBUG_ASSERT (self != NULL);
+ DEBUG_ASSERT (alloc->mode == MODE_ALLOC);
+ DEBUG_ASSERT (alloc->len < G_MAXINT);
+ DEBUG_ASSERT (alloc->len > 0);
+
+ old_items = alloc->items;
+
+ alloc->items_len <<= 1;
+ alloc->items = g_realloc_n (alloc->items, alloc->items_len, sizeof (GList));
+
+ if (G_LIKELY (old_items == alloc->items))
+ return;
+
+ if (alloc->len > 0)
+ alloc->items [0].next = &alloc->items [1];
+
+ for (i = 1; i < (alloc->len - 1); i++)
+ {
+ alloc->items [i].prev = &alloc->items [i-1];
+ alloc->items [i].next = &alloc->items [i+1];
+ }
+
+ if (alloc->len > 1)
+ alloc->items [alloc->len - 1].prev = &alloc->items [alloc->len - 1 - 1];
+}
+
+static void
+_g_array_list_transition (GArrayList *self)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *items;
+ gsize i;
+
+ DEBUG_ASSERT (self != NULL);
+ DEBUG_ASSERT (embed->mode == MODE_EMBED);
+ DEBUG_ASSERT (embed->len == G_N_ELEMENTS (embed->items));
+
+ items = g_malloc_n (G_ARRAY_LIST_DEFAULT_ALLOC, sizeof (GList));
+
+ items [0].prev = NULL;
+ items [0].data = embed->items [0].data;
+ items [0].next = &items [1];
+
+ for (i = 1; i < (G_N_ELEMENTS (embed->items) - 1); i++)
+ {
+ items [i].prev = &items [i - 1];
+ items [i].next = &items [i + 1];
+ items [i].data = &embed->items [i].data;
+ }
+
+ items [G_N_ELEMENTS (embed->items) - 1].prev = &items [G_N_ELEMENTS (embed->items) - 1 - 1];
+ items [G_N_ELEMENTS (embed->items) - 1].next = NULL;
+ items [G_N_ELEMENTS (embed->items) - 1].data = embed->items [G_N_ELEMENTS (embed->items) - 1].data;
+
+ any->mode = MODE_ALLOC;
+
+ alloc->items_len = G_ARRAY_LIST_DEFAULT_ALLOC;
+ alloc->items = items;
+}
+
+void
+g_array_list_add (GArrayList *self,
+ gpointer data)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *item;
+ GList *prev;
+
+ g_return_if_fail (self != NULL);
+
+ if (G_LIKELY (any->mode == MODE_EMBED))
+ {
+ if (G_LIKELY (embed->len < G_N_ELEMENTS (embed->items)))
+ {
+ if (G_LIKELY (embed->len == 0))
+ {
+ embed->items [0].prev = NULL;
+ embed->items [0].next = NULL;
+ embed->items [0].data = data;
+ }
+ else
+ {
+ prev = &embed->items [embed->len - 1];
+ item = &embed->items [embed->len];
+
+ item->prev = prev;
+ item->next = NULL;
+ item->data = data;
+
+ prev->next = item;
+ }
+
+ embed->len++;
+
+ DEBUG_ASSERT (embed->len <= G_N_ELEMENTS (embed->items));
+ DEBUG_ASSERT (embed->len > 0);
+ DEBUG_ASSERT (embed->items [embed->len - 1].data == data);
+
+ return;
+ }
+
+ _g_array_list_transition (self);
+ }
+
+ DEBUG_ASSERT (any->mode == MODE_ALLOC);
+
+ if (G_UNLIKELY (alloc->len == alloc->items_len))
+ _g_array_list_grow (self);
+
+ item = &alloc->items [alloc->len];
+ prev = (alloc->len > 0) ? &alloc->items [alloc->len - 1] : NULL;
+
+ item->data = data;
+ item->prev = NULL;
+ item->next = NULL;
+
+ if (prev)
+ prev->next = item;
+
+ alloc->len++;
+
+ DEBUG_ASSERT (alloc->len <= alloc->items_len);
+ DEBUG_ASSERT (alloc->len > 0);
+ DEBUG_ASSERT (alloc->items [alloc->len - 1].data == data);
+}
+
+void
+g_array_list_remove_index (GArrayList *self,
+ guint index)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *items;
+ gpointer data = NULL;
+ gsize i;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (index < self->len);
+
+ if (G_LIKELY (any->mode == MODE_EMBED))
+ {
+ DEBUG_ASSERT (index < G_N_ELEMENTS (embed->items));
+
+ data = embed->items [index].data;
+ embed->len--;
+ items = embed->items;
+ }
+ else
+ {
+ DEBUG_ASSERT (any->mode == MODE_ALLOC);
+ DEBUG_ASSERT (index < alloc->items_len);
+
+ data = alloc->items [index].data;
+ alloc->len--;
+ items = alloc->items;
+ }
+
+ if (index != alloc->len)
+ memmove (&items [index],
+ &items [index + 1],
+ sizeof (GList) * (any->len - index));
+
+ for (i = index; i < any->len; i++)
+ {
+ if (i > 0)
+ items [i].prev = &items [i - 1];
+ else
+ items [i].prev = NULL;
+
+ if (i < (any->len - 1))
+ items [i].next = &items [i + 1];
+ else
+ items [i].next = NULL;
+ }
+
+ if (any->destroy)
+ any->destroy (data);
+}
+
+void
+g_array_list_remove (GArrayList *self,
+ gpointer data)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *items;
+ gsize i;
+
+ g_return_if_fail (self != NULL);
+
+ items = (any->mode == MODE_EMBED) ? embed->items : alloc->items;
+
+ for (i = 0; i < any->len; i++)
+ {
+ if (items [i].data == data)
+ {
+ g_array_list_remove_index (self, i);
+ return;
+ }
+ }
+
+ g_warning ("Failed to locate %p in GArrayList", data);
+}
+
+void
+g_array_list_destroy (GArrayList *self)
+{
+ GArrayListAny *any = (GArrayListAny *)self;
+ GArrayListEmbed *embed = (GArrayListEmbed *)self;
+ GArrayListAlloc *alloc = (GArrayListAlloc *)self;
+ GList *items;
+ gsize i;
+
+ g_return_if_fail (self != NULL);
+
+ if (any->mode == MODE_EMBED)
+ items = embed->items;
+ else
+ items = alloc->items;
+
+ if (any->destroy)
+ for (i = 0; i < any->len; i++)
+ any->destroy (items [i].data);
+
+ if (any->mode == MODE_ALLOC)
+ g_free (alloc->items);
+
+ if (any->on_heap)
+ g_slice_free (GArrayListAny, any);
+}
diff --git a/glib/garraylist.h b/glib/garraylist.h
new file mode 100644
index 000000000..b8859086f
--- /dev/null
+++ b/glib/garraylist.h
@@ -0,0 +1,76 @@
+/* garraylist.h
+ *
+ * Copyright (C) 2015 Christian Hergert
+ *
+ * This file 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 file 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 General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __G_ARRAY_LIST_H__
+#define __G_ARRAY_LIST_H__
+
+#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ guint len;
+
+ guint padding;
+ gpointer padding1;
+ gpointer padding2;
+ gpointer padding3;
+ gpointer padding4;
+ gpointer padding5;
+ gpointer padding6;
+ gpointer padding7;
+} GArrayList;
+
+GLIB_AVAILABLE_IN_2_46
+GArrayList *g_array_list_new (GDestroyNotify notify);
+
+GLIB_AVAILABLE_IN_2_46
+void g_array_list_init (GArrayList *list,
+ GDestroyNotify notify);
+
+GLIB_AVAILABLE_IN_2_46
+const GList *g_array_list_peek (GArrayList *list);
+
+GLIB_AVAILABLE_IN_2_46
+gpointer g_array_list_index (GArrayList *list,
+ guint index);
+
+GLIB_AVAILABLE_IN_2_46
+void g_array_list_add (GArrayList *list,
+ gpointer data);
+
+GLIB_AVAILABLE_IN_2_46
+void g_array_list_remove (GArrayList *list,
+ gpointer data);
+
+GLIB_AVAILABLE_IN_2_46
+void g_array_list_remove_index (GArrayList *list,
+ guint index);
+
+GLIB_AVAILABLE_IN_2_46
+void g_array_list_destroy (GArrayList *list);
+
+G_END_DECLS
+
+#endif /* __G_ARRAY_LIST_H__ */
diff --git a/glib/glib.h b/glib/glib.h
index 1212d139f..7b959f053 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include
#include
diff --git a/glib/tests/.gitignore b/glib/tests/.gitignore
index c421004ee..1a4babdce 100644
--- a/glib/tests/.gitignore
+++ b/glib/tests/.gitignore
@@ -3,6 +3,7 @@
642026
642026-ec
array-test
+arraylist
asyncqueue
atomic
autoptr
diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am
index 23f29401e..3da632a67 100644
--- a/glib/tests/Makefile.am
+++ b/glib/tests/Makefile.am
@@ -31,6 +31,7 @@ test_extra_programs = \
test_programs = \
array-test \
+ arraylist \
asyncqueue \
base64 \
bitlock \
diff --git a/glib/tests/arraylist.c b/glib/tests/arraylist.c
new file mode 100644
index 000000000..8d92acb3d
--- /dev/null
+++ b/glib/tests/arraylist.c
@@ -0,0 +1,100 @@
+/* arraylist.c
+ *
+ * Copyright (C) 2015 Christian Hergert
+ *
+ * This 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 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include
+
+static int test_basic_counter;
+
+static void
+test_basic_destroy (gpointer data)
+{
+ test_basic_counter++;
+}
+
+static void
+test_basic (GArrayList *al)
+{
+ const GList *iter, *list;
+ gsize i;
+ gsize counter = 0;
+
+ g_assert (al != NULL);
+ g_assert_cmpint (al->len, ==, 0);
+
+ for (i = 1; i <= 1000; i++)
+ {
+ g_array_list_add (al, GSIZE_TO_POINTER (i));
+ g_assert_cmpint (al->len, ==, i);
+ }
+
+ list = g_array_list_peek (al);
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ counter++;
+ g_assert_cmpint (counter, ==, GPOINTER_TO_SIZE (iter->data));
+ }
+
+ g_assert_cmpint (counter, ==, 1000);
+
+ for (i = 1; i <= 500; i++)
+ {
+ gpointer item = GSIZE_TO_POINTER (i);
+ gpointer val = g_array_list_index (al, 0);
+ g_assert_cmpint (GPOINTER_TO_SIZE (val), ==, i);
+ g_array_list_remove (al, item);
+ }
+
+ g_assert_cmpint (al->len, ==, 500);
+ g_assert_cmpint (test_basic_counter, ==, 500);
+ g_array_list_destroy (al);
+ g_assert_cmpint (test_basic_counter, ==, 1000);
+
+ test_basic_counter = 0;
+}
+
+static void
+test_basic_alloc (void)
+{
+ test_basic (g_array_list_new (test_basic_destroy));
+}
+
+static void
+test_basic_stack (void)
+{
+ GArrayList al;
+
+ g_array_list_init (&al, test_basic_destroy);
+ test_basic (&al);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugzilla.gnome.org/");
+
+ g_test_add_func ("/GArrayList/heap", test_basic_alloc);
+ g_test_add_func ("/GArrayList/stack", test_basic_stack);
+
+ return g_test_run ();
+}