[glib/halfline/debug-metrics: 4/12] gmetrics: Add debug api for print metrics




commit 27c96b231a78f0f439aca9121aa162f201035f4a
Author: Ray Strode <rstrode redhat com>
Date:   Sun Jun 27 18:02:10 2021 -0400

    gmetrics: Add debug api for print metrics
    
    In order to get a handle on memory usage it would be good to get
    reports of various types of object usage over time.
    
    This commit provides a little api for generating CSV files to
    dump the data to.

 glib/Makefile.am |    6 +-
 glib/glib-init.c |    1 +
 glib/glib-init.h |    1 +
 glib/glib.h      |    1 +
 glib/gmessages.c |    2 +
 glib/gmessages.h |    1 -
 glib/gmetrics.c  | 2781 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 glib/gmetrics.h  |  333 +++++++
 glib/uthash.h    | 1138 ++++++++++++++++++++++
 glib/utlist.h    | 1073 +++++++++++++++++++++
 glib/utstring.h  |  411 ++++++++
 11 files changed, 5745 insertions(+), 3 deletions(-)
---
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 8da549c7f..983134428 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -143,6 +143,7 @@ libglib_2_0_la_SOURCES =    \
        gmarkup.c               \
        gmem.c                  \
        gmessages.c             \
+       gmetrics.c              \
        gmirroringtable.h       \
        gnode.c                 \
        goption.c               \
@@ -278,6 +279,7 @@ glibsubinclude_HEADERS = \
        gmarkup.h       \
        gmem.h          \
        gmessages.h     \
+       gmetrics.h      \
        gnode.h         \
        goption.h       \
        gpattern.h      \
@@ -351,8 +353,8 @@ pcre_lib = pcre/libpcre.la
 pcre_inc =
 endif
 
-libglib_2_0_la_CFLAGS = $(AM_CFLAGS) $(GLIB_HIDDEN_VISIBILITY_CFLAGS) $(LIBSYSTEMD_CFLAGS)
-libglib_2_0_la_LIBADD = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@ @ICONV_LIBS@ 
@G_LIBS_EXTRA@ $(pcre_lib) $(G_THREAD_LIBS_EXTRA) $(G_THREAD_LIBS_FOR_GTHREAD) $(LIBSYSTEMD_LIBS)
+libglib_2_0_la_CFLAGS = $(AM_CFLAGS) $(GLIB_HIDDEN_VISIBILITY_CFLAGS) $(LIBSYSTEMD_CFLAGS) $(ZLIB_CFLAGS)
+libglib_2_0_la_LIBADD = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@ @ICONV_LIBS@ 
@G_LIBS_EXTRA@ $(pcre_lib) $(G_THREAD_LIBS_EXTRA) $(G_THREAD_LIBS_FOR_GTHREAD) $(LIBSYSTEMD_LIBS) $(ZLIB_LIBS)
 libglib_2_0_la_DEPENDENCIES = libcharset/libcharset.la $(printf_la) @GIO@ @GSPAWN@ @PLATFORMDEP@ 
$(glib_win32_res) $(glib_def)
 
 libglib_2_0_la_LDFLAGS = $(GLIB_LINK_FLAGS) \
diff --git a/glib/glib-init.c b/glib/glib-init.c
index 5f312113a..ef6d7a9f0 100644
--- a/glib/glib-init.c
+++ b/glib/glib-init.c
@@ -268,6 +268,7 @@ glib_init (void)
 
   glib_inited = TRUE;
 
+  g_metrics_init ();
   g_messages_prefixed_init ();
   g_debug_init ();
   g_quark_init ();
diff --git a/glib/glib-init.h b/glib/glib-init.h
index 5da33c052..8d44a0f48 100644
--- a/glib/glib-init.h
+++ b/glib/glib-init.h
@@ -21,6 +21,7 @@
 #define __GLIB_INIT_H__
 
 #include "gmessages.h"
+#include "gmetrics.h"
 
 extern GLogLevelFlags g_log_always_fatal;
 extern GLogLevelFlags g_log_msg_prefix;
diff --git a/glib/glib.h b/glib/glib.h
index 4f5a7f702..33ed0de80 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -60,6 +60,7 @@
 #include <glib/gmarkup.h>
 #include <glib/gmem.h>
 #include <glib/gmessages.h>
+#include <glib/gmetrics.h>
 #include <glib/gnode.h>
 #include <glib/goption.h>
 #include <glib/gpattern.h>
diff --git a/glib/gmessages.c b/glib/gmessages.c
index cbaed5da9..5d2fe1667 100644
--- a/glib/gmessages.c
+++ b/glib/gmessages.c
@@ -202,6 +202,8 @@
 #include <unistd.h>
 #endif
 
+#include <zlib.h>
+
 #ifdef G_OS_WIN32
 #include <process.h>           /* For getpid() */
 #include <io.h>
diff --git a/glib/gmessages.h b/glib/gmessages.h
index cebdc1659..6690f95cc 100644
--- a/glib/gmessages.h
+++ b/glib/gmessages.h
@@ -618,7 +618,6 @@ GPrintFunc      g_set_printerr_handler  (GPrintFunc      func);
      return (val);                     }G_STMT_END
 
 #endif /* !G_DISABLE_CHECKS */
-
 G_END_DECLS
 
 #endif /* __G_MESSAGES_H__ */
diff --git a/glib/gmetrics.c b/glib/gmetrics.c
new file mode 100644
index 000000000..6012ed335
--- /dev/null
+++ b/glib/gmetrics.c
@@ -0,0 +1,2781 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+/*
+ * MT safe
+ */
+
+#include "config.h"
+
+#include <execinfo.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <locale.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+
+#define uthash_malloc(_s) g_metrics_allocation_block_store_allocate_with_name 
(metrics_allocation_block_store, _s, "uthash_malloc")
+#define uthash_free(_p,_s) g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, _p)
+#include <uthash.h>
+#include <utlist.h>
+#include <utstring.h>
+
+
+#include "glib-init.h"
+#include "galloca.h"
+#include "gbacktrace.h"
+#include "gcharset.h"
+#include "gconvert.h"
+#include "genviron.h"
+#include "gfileutils.h"
+#include "gmain.h"
+#include "gmem.h"
+#include "gmetrics.h"
+#include "gprintfint.h"
+#include "gtestutils.h"
+#include "gthread.h"
+#include "gstdio.h"
+#include "gstrfuncs.h"
+#include "gstring.h"
+#include "gtimer.h"
+
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <zlib.h>
+
+extern void *__libc_malloc (size_t size);
+extern void *__libc_realloc (void *ptr, size_t size);
+extern void *__libc_calloc (size_t num, size_t size);
+extern void __libc_free (void *ptr);
+typedef struct _GMetricsConfig GMetricsConfig;
+typedef struct _GMetricsAllocationHeader GMetricsAllocationHeader;
+typedef union _GMetricsAllocationBlock GMetricsAllocationBlock;
+typedef struct _GMetricsAllocation GMetricsAllocation;
+typedef struct _GMetricsAllocationBlockStore GMetricsAllocationBlockStore;
+typedef struct _GMetricsAllocationBlockStoreIter GMetricsAllocationBlockStoreIter;
+typedef struct _GMetricsAllocationBlocksIter GMetricsAllocationBlocksIter;
+typedef struct _GMetricsListNode GMetricsListNode;
+
+#define round_to_multiple(n, m) (((n) + (((m) - 1))) & ~((m) - 1))
+
+struct _GMetricsConfig
+{
+  char log_dir[1024];
+  char skipped_metrics[512];
+  char included_metrics[512];
+  char collection_ignore_list[512];
+  char collection_include_list[512];
+  guint collection_interval;
+  int stack_trace_size;
+  int stack_trace_annotation_size;
+  gsize max_allocation_block_stores;
+  gsize allocation_block_store_size;
+  gsize dedicated_allocation_block_store_threshold;
+  gsize generations_to_settle;
+  gsize generations_to_reset_average_window;
+  gsize number_of_interesting_instances;
+  gsize stack_trace_sample_interval;
+  guint32 metrics_enabled : 1;
+  guint32 override_system_malloc : 1;
+  guint32 override_glib_malloc : 1;
+  guint32 validate_allocation_blocks : 1;
+  guint32 use_map_files : 1;
+};
+
+struct _GMetricsFile
+{
+  gzFile gzipped_file;
+  char *static_format_string;
+  char *variadic_format_string;
+  gdouble now;
+};
+
+struct _GMetricsAllocationHeader
+{
+  char name[64];
+  guint32 is_freed;
+  gsize number_of_blocks;
+  GMetricsAllocationBlock *previous_block;
+};
+
+union _GMetricsAllocationBlock
+{
+  GMetricsAllocationHeader header;
+  char payload[128];
+};
+
+struct _GMetricsAllocation
+{
+  GMetricsAllocationBlock header_block;
+  GMetricsAllocationBlock payload_blocks[0];
+};
+
+typedef struct _GMetricsAllocationFileMap GMetricsAllocationFileMap;
+struct _GMetricsAllocationFileMap
+{
+  GMetricsAllocationBlockStore *block_store;
+  union
+  {
+    char *map_address;
+    GMetricsAllocationBlock *blocks;
+  };
+  gsize size;
+  gsize number_of_blocks;
+  GMetricsAllocationBlock *large_free_block;
+};
+
+struct _GMetricsAllocationBlockStore
+{
+  char name[128];
+  char thread_name[32];
+  GMetricsStackTrace *stack_trace;
+  GMetricsAllocationFileMap file_map;
+  gsize number_of_allocations;
+  gsize total_bytes_allocated;
+  guint32 is_dedicated : 1;
+  guint32 is_thread_default : 1;
+};
+
+struct _GMetricsAllocationBlockStoreIter
+{
+  GMetricsListIter list_iter;
+};
+
+struct _GMetricsAllocationBlocksIter
+{
+  GMetricsAllocationFileMap *file_map;
+  GMetricsAllocationBlock *starting_block;
+  GMetricsAllocationBlock *previous_block;
+  gsize items_examined;
+};
+
+static void g_metrics_allocation_blocks_iter_init_after_block (GMetricsAllocationBlocksIter *iter,
+                                                               GMetricsAllocationFileMap    *file_map,
+                                                               GMetricsAllocationBlock      *block);
+static gboolean g_metrics_allocation_blocks_iter_next (GMetricsAllocationBlocksIter  *iter,
+                                                       GMetricsAllocationBlock      **next_block);
+
+static gboolean g_metrics_allocation_file_map_is_open (GMetricsAllocationFileMap *file_map);
+
+static volatile gboolean needs_flush;
+static gulong g_metrics_generation;
+static int timeout_fd = -1;
+GMetricsList *timeout_handlers;
+G_LOCK_DEFINE_STATIC (timeouts);
+static GMetricsAllocationBlockStore store_for_allocation_block_stores;
+G_LOCK_DEFINE_STATIC (allocation_block_stores);
+static GMetricsAllocationBlockStore *metrics_allocation_block_store;
+static __thread GMetricsList block_store_stack;
+static GMetricsList allocation_block_stores;
+static GMetricsFile *allocation_block_store_metrics_file;
+G_LOCK_DEFINE_STATIC (allocations);
+
+static __thread GMetricsStackTraceAnnotationHandler stack_trace_annotation_handler;
+static __thread gpointer stack_trace_annotation_handler_user_data;
+
+static GMetricsConfig metrics_config;
+
+static const char *default_skipped_metrics="arrays lists metrics-allocations objects-by-type ptr-arrays 
signals";
+static const char *default_collection_ignore_list="Handler GSList";
+
+static char *
+int_to_string (guint64  integer,
+               char    *string,
+               gsize    string_size)
+{
+  gsize i, j, bytes_used;
+  char swap_byte;
+
+  bytes_used = 0;
+  while (integer != 0 && bytes_used < string_size)
+    {
+      string[bytes_used++] = (integer % 10) + '0';
+      integer /= 10;
+    }
+  string[bytes_used] = '\0';
+
+  j = bytes_used - 1;
+  for (i = 0; i < j; i++, j--)
+    {
+      swap_byte = string[i];
+      string[i] = string[j];
+      string[j] = swap_byte;
+    }
+  return string + bytes_used;
+}
+
+gboolean
+g_metrics_enabled (void)
+{
+  return metrics_config.metrics_enabled;
+}
+
+static gsize
+g_metrics_allocation_get_payload_size (GMetricsAllocation *allocation)
+{
+  GMetricsAllocationHeader *header;
+  header = &allocation->header_block.header;
+
+  return (header->number_of_blocks * sizeof (GMetricsAllocationBlock)) - sizeof (allocation->header_block);
+}
+
+static void
+g_metrics_allocation_file_map_validate_block (GMetricsAllocationFileMap *file_map,
+                                              GMetricsAllocationBlock   *block)
+{
+  GMetricsAllocation *allocation;
+  GMetricsAllocationHeader *header;
+
+  if (!metrics_config.validate_allocation_blocks)
+    return;
+
+  if (!block)
+    return;
+
+  allocation = (GMetricsAllocation *) block;
+  header = &allocation->header_block.header;
+
+  if (header->is_freed != 1 && header->is_freed != 0)
+    G_BREAKPOINT ();
+
+  if (header->number_of_blocks == 0)
+    G_BREAKPOINT ();
+
+  if (header->number_of_blocks > file_map->number_of_blocks)
+    G_BREAKPOINT ();
+
+  if (header->previous_block != NULL)
+    {
+      GMetricsAllocationHeader *previous_header;
+
+      if (header->previous_block < file_map->blocks)
+        G_BREAKPOINT ();
+
+      previous_header = (GMetricsAllocationHeader *) header->previous_block;
+
+      if (previous_header->number_of_blocks == 0)
+        G_BREAKPOINT ();
+
+      if (previous_header->number_of_blocks > file_map->number_of_blocks)
+        G_BREAKPOINT ();
+
+      if (header->previous_block + previous_header->number_of_blocks != block)
+        G_BREAKPOINT ();
+    }
+
+  if (block + header->number_of_blocks < file_map->blocks + file_map->number_of_blocks)
+    {
+      GMetricsAllocationBlock *next_block;
+      GMetricsAllocationHeader *next_header;
+
+      next_block = block + header->number_of_blocks;
+      next_header = (GMetricsAllocationHeader *) next_block;
+
+      if (next_header->number_of_blocks == 0)
+        G_BREAKPOINT ();
+
+      if (next_header->number_of_blocks > file_map->number_of_blocks)
+        G_BREAKPOINT ();
+
+      if (next_header->previous_block != block)
+        G_BREAKPOINT ();
+    }
+}
+
+static gboolean
+g_metrics_allocation_file_map_open (GMetricsAllocationFileMap *file_map,
+                                    gsize                      size)
+{
+  GMetricsAllocationBlockStore *block_store;
+  char filename[1024] = "";
+  int result;
+  int map_fd = -1;
+  int mmap_flags;
+
+  file_map->size = size;
+
+  block_store = file_map->block_store;
+  snprintf (filename, sizeof (filename) - 1, "/var/tmp/user-%d-for-pid-%d-%s-%p.map", getuid (), getpid (), 
block_store->name, file_map);
+  unlink (filename);
+
+  map_fd = open (filename, O_RDWR | O_CREAT, 0644);
+
+  if (map_fd < 0)
+    goto fail;
+
+  result = ftruncate (map_fd, file_map->size);
+
+  unlink (filename);
+
+  if (result < 0)
+    goto fail;
+
+  if (metrics_config.use_map_files)
+    mmap_flags = MAP_SHARED;
+  else
+    mmap_flags = MAP_PRIVATE;
+
+  file_map->map_address = mmap (NULL, file_map->size, PROT_READ | PROT_WRITE, mmap_flags, map_fd, 0);
+
+  if (file_map->map_address == MAP_FAILED)
+    goto fail;
+
+  close (map_fd);
+  map_fd = -1;
+
+  file_map->number_of_blocks = file_map->size / sizeof (GMetricsAllocationBlock);
+
+  file_map->blocks[0].header.number_of_blocks = file_map->number_of_blocks;
+  file_map->blocks[0].header.is_freed = TRUE;
+  file_map->blocks[0].header.previous_block = NULL;
+
+  file_map->large_free_block = &file_map->blocks[0];
+
+  return TRUE;
+
+fail:
+  if (map_fd >= 0)
+    {
+      close (map_fd);
+      map_fd = -1;
+    }
+  file_map->size = 0;
+  file_map->map_address = MAP_FAILED;
+  return FALSE;
+}
+
+static void
+g_metrics_allocation_file_map_close (GMetricsAllocationFileMap *file_map)
+{
+  munmap (file_map->map_address, file_map->size);
+  file_map->map_address = MAP_FAILED;
+}
+
+static gboolean
+g_metrics_allocation_file_map_is_open (GMetricsAllocationFileMap *file_map)
+{
+  return file_map->map_address != MAP_FAILED;
+}
+
+static gboolean
+g_metrics_allocation_file_map_has_allocation (GMetricsAllocationFileMap *file_map,
+                                              gpointer                   allocation)
+{
+  char *allocation_bytes = allocation;
+  return allocation_bytes >= file_map->map_address && allocation_bytes < (file_map->map_address + 
file_map->size);
+}
+
+static gsize
+g_metrics_allocation_file_map_get_index_of_block (GMetricsAllocationFileMap  *file_map,
+                                                     GMetricsAllocationBlock *block)
+{
+  return block - file_map->blocks;
+}
+
+static void
+g_metrics_allocation_file_map_shrink_allocation (GMetricsAllocationFileMap *file_map,
+                                                 GMetricsAllocation           *allocation,
+                                                 gsize                         number_of_blocks)
+{
+  GMetricsAllocationBlockStore *block_store;
+  GMetricsAllocationHeader *header;
+  GMetricsAllocationBlock *first_block;
+  gsize blocks_left;
+
+  block_store = file_map->block_store;
+  header = &allocation->header_block.header;
+  first_block = (GMetricsAllocationBlock *) allocation;
+
+  blocks_left = header->number_of_blocks - number_of_blocks;
+  header->number_of_blocks = number_of_blocks;
+
+  if (blocks_left > 0)
+    {
+      GMetricsAllocationBlock *next_block, *block_after_next;
+      GMetricsAllocation *next_allocation;
+      GMetricsAllocationHeader *next_header;
+
+      next_block = first_block + number_of_blocks;
+
+      if (metrics_config.validate_allocation_blocks)
+        {
+          if (next_block >= &file_map->blocks[file_map->number_of_blocks])
+            G_BREAKPOINT ();
+        }
+
+      next_allocation = (GMetricsAllocation *) next_block;
+      next_header = &next_allocation->header_block.header;
+
+      next_header->number_of_blocks = blocks_left;
+      next_header->is_freed = TRUE;
+      next_header->previous_block = first_block;
+
+      if (file_map->large_free_block == NULL
+          || file_map->large_free_block->header.number_of_blocks < next_header->number_of_blocks)
+        file_map->large_free_block = next_block;
+
+      block_store->total_bytes_allocated -= next_header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+
+      block_after_next = next_block + next_header->number_of_blocks;
+
+      if (block_after_next < &file_map->blocks[file_map->number_of_blocks])
+        {
+          GMetricsAllocation *allocation_after_next;
+          GMetricsAllocationHeader *header_after_next;
+
+          allocation_after_next = (GMetricsAllocation *) block_after_next;
+          header_after_next = &allocation_after_next->header_block.header;
+          header_after_next->previous_block = next_block;
+          if (metrics_config.validate_allocation_blocks)
+            {
+              if (next_block >= &file_map->blocks[file_map->number_of_blocks])
+                G_BREAKPOINT ();
+            }
+        }
+    }
+}
+
+
+static GMetricsAllocationBlock *
+find_next_free_block (GMetricsAllocationFileMap *file_map,
+                      GMetricsAllocationBlock   *allocated_block)
+{
+  GMetricsAllocationBlock *block, *next_block;
+
+  block = allocated_block;
+
+  g_metrics_allocation_file_map_validate_block (file_map, block);
+
+  do
+    {
+      if (block + block->header.number_of_blocks >= file_map->blocks + file_map->number_of_blocks)
+          return NULL;
+
+      next_block = block + block->header.number_of_blocks;
+
+      if (next_block->header.is_freed)
+        {
+          g_metrics_allocation_file_map_validate_block (file_map, next_block);
+
+          return next_block;
+        }
+
+      block = next_block;
+    }
+  while (block <= file_map->blocks + file_map->number_of_blocks);
+
+  return NULL;
+}
+
+static void
+consolidate_consecutive_blocks (GMetricsAllocationFileMap *file_map,
+                                GMetricsAllocationBlock   *block,
+                                gsize                      blocks_needed)
+{
+  GMetricsAllocation *allocation = NULL;
+  GMetricsAllocationHeader *header;
+  GMetricsAllocationBlocksIter look_ahead_iter;
+  GMetricsAllocationBlock *look_ahead_block;
+  GMetricsAllocationBlock *next_block = NULL;
+
+  allocation = (GMetricsAllocation *) block;
+  header = &allocation->header_block.header;
+
+  if (header->number_of_blocks >= blocks_needed)
+    return;
+
+  g_metrics_allocation_blocks_iter_init_after_block (&look_ahead_iter, file_map, block);
+  while (g_metrics_allocation_blocks_iter_next (&look_ahead_iter, &look_ahead_block))
+    {
+      GMetricsAllocation *look_ahead_allocation;
+      GMetricsAllocationHeader *look_ahead_header;
+
+      if (look_ahead_block >= &file_map->blocks[file_map->number_of_blocks])
+        G_BREAKPOINT ();
+
+      look_ahead_allocation = (GMetricsAllocation *) look_ahead_block;
+      look_ahead_header = &look_ahead_allocation->header_block.header;
+
+      if (look_ahead_block < block)
+        break;
+
+      if (!look_ahead_header->is_freed)
+        break;
+
+      header->number_of_blocks += look_ahead_header->number_of_blocks;
+
+      if (metrics_config.validate_allocation_blocks)
+        memset (look_ahead_block, 0xaa, MIN (look_ahead_header->number_of_blocks, 4) * sizeof 
(GMetricsAllocationBlock));
+
+      if (header->number_of_blocks >= blocks_needed)
+        break;
+
+      g_metrics_allocation_blocks_iter_init_after_block (&look_ahead_iter, file_map, block);
+    }
+
+  if (block + header->number_of_blocks < &file_map->blocks[file_map->number_of_blocks])
+    {
+      next_block = block + header->number_of_blocks;
+      next_block->header.previous_block = block;
+
+      if (block >= &file_map->blocks[file_map->number_of_blocks])
+        G_BREAKPOINT ();
+
+      g_metrics_allocation_file_map_validate_block (file_map, next_block);
+    }
+
+  g_metrics_allocation_file_map_validate_block (file_map, block);
+
+  if (file_map->large_free_block < block + header->number_of_blocks)
+    {
+      if (file_map->large_free_block > block)
+        {
+          file_map->large_free_block = block;
+        }
+      else if (file_map->large_free_block != NULL && next_block != NULL)
+        {
+          g_metrics_allocation_file_map_validate_block (file_map, next_block);
+          if (file_map->large_free_block->header.number_of_blocks < next_block->header.number_of_blocks)
+            file_map->large_free_block = next_block;
+        }
+    }
+}
+
+static void
+g_metrics_allocation_file_map_claim_allocation (GMetricsAllocationFileMap *file_map,
+                                                GMetricsAllocation             *allocation)
+{
+  GMetricsAllocationBlockStore *block_store;
+  GMetricsAllocationHeader *header;
+  GMetricsAllocationBlock *block;
+
+  block_store = file_map->block_store;
+  header = &allocation->header_block.header;
+  block = (GMetricsAllocationBlock *) allocation;
+
+  header->is_freed = FALSE;
+  block_store->total_bytes_allocated += header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+  block_store->number_of_allocations++;
+
+  if (file_map->large_free_block == block)
+    file_map->large_free_block = find_next_free_block (file_map, block);
+}
+
+static gboolean
+g_metrics_allocation_file_map_grow_allocation (GMetricsAllocationFileMap    *file_map,
+                                               GMetricsAllocation                *allocation,
+                                               gsize                              number_of_blocks)
+{
+  GMetricsAllocationHeader *header;
+  GMetricsAllocationBlock *first_block;
+  GMetricsAllocationBlockStore *block_store;
+  gsize old_size;
+
+  block_store = file_map->block_store;
+  header = &allocation->header_block.header;
+  first_block = (GMetricsAllocationBlock *) allocation;
+
+  old_size = header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+  consolidate_consecutive_blocks (file_map, first_block, number_of_blocks);
+
+  block_store->total_bytes_allocated -= old_size;
+  block_store->total_bytes_allocated += header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+
+  if (header->number_of_blocks > number_of_blocks)
+    g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, number_of_blocks);
+
+  g_metrics_allocation_file_map_validate_block (file_map, first_block);
+
+  return header->number_of_blocks == number_of_blocks;
+}
+
+static void
+g_metrics_allocation_file_map_release_allocation (GMetricsAllocationFileMap *file_map,
+                                                  GMetricsAllocation        *allocation)
+{
+  GMetricsAllocationBlockStore *block_store;
+  GMetricsAllocationHeader *header;
+  GMetricsAllocationBlock *block, *previous_block;
+
+  block_store = file_map->block_store;
+  header = &allocation->header_block.header;
+  block = (GMetricsAllocationBlock *) allocation;
+
+  if (metrics_config.validate_allocation_blocks)
+    {
+      if (block < &file_map->blocks[0])
+          G_BREAKPOINT ();
+
+      if (block >= &file_map->blocks[file_map->number_of_blocks])
+          G_BREAKPOINT ();
+    }
+
+  if (header->is_freed)
+    G_BREAKPOINT ();
+
+  g_metrics_allocation_file_map_validate_block (file_map, block);
+
+  header->is_freed = TRUE;
+  block_store->total_bytes_allocated -= header->number_of_blocks * sizeof (GMetricsAllocationBlock);
+  block_store->number_of_allocations--;
+
+  g_metrics_allocation_file_map_validate_block (file_map, file_map->large_free_block);
+
+  if (file_map->large_free_block == NULL
+      || file_map->large_free_block->header.number_of_blocks < header->number_of_blocks)
+    file_map->large_free_block = block;
+
+  if (block->header.previous_block)
+    {
+      previous_block = (GMetricsAllocationBlock *) block->header.previous_block;
+
+      if (previous_block->header.is_freed)
+        consolidate_consecutive_blocks (file_map,
+                                        previous_block,
+                                        previous_block->header.number_of_blocks + header->number_of_blocks);
+    }
+}
+
+static gboolean
+g_metrics_allocation_block_store_init (GMetricsAllocationBlockStore *block_store,
+                                       const char                   *name,
+                                       gsize                         size)
+{
+  gboolean file_map_mapped;
+
+  strncpy (block_store->name, name, sizeof (block_store->name) - 1);
+
+  block_store->file_map.block_store = block_store;
+  file_map_mapped = g_metrics_allocation_file_map_open (&block_store->file_map, size);
+
+  return file_map_mapped;
+}
+
+void
+g_metrics_allocation_block_store_free (GMetricsAllocationBlockStore *block_store)
+{
+  G_LOCK (allocation_block_stores);
+  g_metrics_allocation_file_map_close (&block_store->file_map);
+  g_metrics_list_remove_item (&allocation_block_stores, block_store);
+  g_metrics_stack_trace_free (block_store->stack_trace);
+  g_metrics_allocation_block_store_deallocate (&store_for_allocation_block_stores,
+                                               block_store);
+  G_UNLOCK (allocation_block_stores);
+}
+
+GMetricsAllocationBlockStore *
+g_metrics_allocation_block_store_new (const char *name,
+                                      gsize       size)
+{
+  GMetricsAllocationBlockStore *block_store;
+  gboolean initialized;
+  char thread_name[32] = "thread-";
+  pid_t current_thread_id;
+
+  current_thread_id = (pid_t) syscall (SYS_gettid);
+  int_to_string (current_thread_id, thread_name + strlen (thread_name), sizeof (thread_name) - strlen 
(thread_name));
+
+  if (name == NULL)
+    name = thread_name;
+
+  G_LOCK (allocation_block_stores);
+  block_store = g_metrics_allocation_block_store_allocate_with_name (&store_for_allocation_block_stores,
+                                                                     sizeof (GMetricsAllocationBlockStore),
+                                                                     "GMetricsAllocationBlockStore");
+  G_UNLOCK (allocation_block_stores);
+
+  memset (block_store, 0, sizeof (GMetricsAllocationBlockStore));
+
+  strncpy (block_store->thread_name, thread_name, sizeof (block_store->thread_name) - 1);
+
+  initialized = g_metrics_allocation_block_store_init (block_store, name, size);
+
+  if (!initialized)
+    {
+      G_BREAKPOINT ();
+      return NULL;
+    }
+
+  G_LOCK(allocation_block_stores);
+  if (metrics_allocation_block_store)
+    g_metrics_list_add_item (&allocation_block_stores, block_store);
+  G_UNLOCK (allocation_block_stores);
+
+  return block_store;
+}
+
+static gboolean
+g_metrics_allocation_block_store_has_allocation (GMetricsAllocationBlockStore *block_store,
+                                                 gpointer                       allocation)
+{
+  return g_metrics_allocation_file_map_has_allocation (&block_store->file_map, allocation);
+}
+
+static void
+initialize_store_for_allocation_block_stores (void)
+{
+  g_metrics_allocation_block_store_init (&store_for_allocation_block_stores,
+                                         "allocation-block-stores",
+                                         metrics_config.max_allocation_block_stores * sizeof 
(GMetricsAllocationBlockStore));
+}
+
+static void
+allocate_metrics_block_store (void)
+{
+  GMetricsAllocationBlockStore *block_store;
+
+  block_store = g_metrics_allocation_block_store_new ("metrics",
+                                                      metrics_config.allocation_block_store_size);
+
+  if (block_store == NULL)
+    return;
+
+  G_LOCK (allocation_block_stores);
+  metrics_allocation_block_store = block_store;
+  g_metrics_list_add_item (&allocation_block_stores, metrics_allocation_block_store);
+  G_UNLOCK (allocation_block_stores);
+}
+
+static void
+allocate_thread_default_block_store (void)
+{
+  GMetricsAllocationBlockStore *block_store;
+
+  block_store = g_metrics_allocation_block_store_new (NULL, metrics_config.allocation_block_store_size);
+  block_store->is_thread_default = TRUE;
+  g_metrics_push_default_allocation_block_store (block_store);
+}
+
+static void
+g_metrics_allocation_block_store_iter_init (GMetricsAllocationBlockStoreIter *iter)
+{
+  g_metrics_list_iter_init (&iter->list_iter, &allocation_block_stores);
+}
+
+static gboolean
+g_metrics_allocation_block_store_iter_next (GMetricsAllocationBlockStoreIter  *iter,
+                                            GMetricsAllocationBlockStore     **block_store)
+{
+  return g_metrics_list_iter_next (&iter->list_iter, block_store);
+}
+
+static void
+write_allocation_list (GMetricsAllocationBlockStore *block_store)
+{
+  GMetricsAllocationFileMap *file_map;
+  GMetricsAllocationBlocksIter iter;
+  GMetricsAllocationBlock *block;
+  int fd = -1;
+
+  file_map = &block_store->file_map;
+  g_metrics_allocation_blocks_iter_init_after_block (&iter, file_map, NULL);
+  while (g_metrics_allocation_blocks_iter_next (&iter, &block))
+    {
+      GMetricsAllocation *allocation;
+      GMetricsAllocationHeader *header;
+
+      allocation = (GMetricsAllocation *) block;
+      header = (GMetricsAllocationHeader *) &allocation->header_block.header;
+
+      if (header->is_freed)
+        {
+          consolidate_consecutive_blocks (file_map,
+                                          block,
+                                          file_map->number_of_blocks);
+          continue;
+        }
+
+      if (header->name[0] != '\0')
+        {
+          if (fd == -1)
+            {
+              char filename[1024] = { 0 };
+              strncat (filename, metrics_config.log_dir, sizeof (filename) - 1);
+              strncat (filename, "/", sizeof (filename) - 1);
+              strncat (filename, block_store->name, sizeof (filename) - 1);
+              strncat (filename, "-allocations.list", sizeof (filename) - 1);
+              fd = open (filename, O_CREAT | O_TRUNC | O_RDWR, 0644);
+            }
+
+          write (fd, header->name, strlen (header->name));
+          write (fd, "\n", 1);
+        }
+    }
+  if (fd != -1)
+    close (fd);
+}
+
+static void
+on_allocation_block_stores_metrics_timeout (void)
+{
+  GMetricsAllocationBlockStoreIter iter;
+  GMetricsAllocationBlockStore *block_store = NULL;
+
+  if (!allocation_block_store_metrics_file)
+    return;
+
+  G_LOCK (allocation_block_stores);
+
+  G_LOCK (allocations);
+  if (g_metrics_requested ("metrics-allocations")) 
+      write_allocation_list (metrics_allocation_block_store);
+  G_UNLOCK (allocations);
+
+  if (!g_metrics_requested ("allocation-block-stores"))
+    return;
+
+  g_metrics_file_start_record (allocation_block_store_metrics_file);
+  g_metrics_allocation_block_store_iter_init (&iter);
+  while (g_metrics_allocation_block_store_iter_next (&iter, &block_store))
+    {
+       const char *stack_trace = NULL;
+       GMetricsAllocationFileMap *file_map;
+
+       file_map = &block_store->file_map;
+
+       if (!g_metrics_allocation_file_map_is_open (file_map))
+         continue;
+
+       if (block_store->stack_trace != NULL)
+         stack_trace = g_metrics_stack_trace_get_output (block_store->stack_trace);
+
+       g_metrics_file_add_row (allocation_block_store_metrics_file,
+                               block_store->name,
+                               block_store->thread_name,
+                               block_store->number_of_allocations,
+                               block_store->total_bytes_allocated,
+                               stack_trace?: "");
+    }
+  g_metrics_file_end_record (allocation_block_store_metrics_file);
+  G_UNLOCK (allocation_block_stores);
+}
+
+static gsize
+get_int_from_environment (const char *variable,
+                          gsize       default_value)
+{
+  const char *value;
+
+  value = getenv (variable);
+
+  if (value == NULL)
+    return default_value;
+
+  return strtol (value, NULL, 10);
+}
+
+static void
+load_metrics_config_command (void)
+{
+  static char cmdline[1024] = { 0 };
+  const char *requested_command;
+  const char *requested_user, *current_user;
+  int fd;
+
+  fd = open ("/proc/self/cmdline", O_RDONLY);
+  if (fd >= 0)
+    {
+      read (fd, cmdline, 1023);
+      close (fd);
+    }
+
+  current_user = getenv ("USER");
+  requested_user = getenv ("G_METRICS_USER");
+  requested_command = getenv ("G_METRICS_COMMAND")? : "gnome-shell";
+
+  metrics_config.metrics_enabled = g_str_has_suffix (cmdline, requested_command) &&
+                                   g_strcmp0 (current_user, requested_user) == 0;
+}
+
+static void
+load_metrics_allocation_config (void)
+{
+  metrics_config.max_allocation_block_stores = get_int_from_environment 
("G_METRICS_MAX_ALLOCATION_BLOCK_STORES", 8192);
+  metrics_config.allocation_block_store_size = get_int_from_environment 
("G_METRICS_DEFAULT_ALLOCATION_BLOCK_STORE_SIZE", 10485760) * 1024L;
+  metrics_config.dedicated_allocation_block_store_threshold = get_int_from_environment 
("G_METRICS_DEDICATED_ALLOCATION_BLOCK_STORE_THRESHOLD", 8192);
+  metrics_config.override_system_malloc = get_int_from_environment ("G_METRICS_OVERRIDE_SYSTEM_MALLOC", 0);
+  metrics_config.override_glib_malloc = get_int_from_environment ("G_METRICS_OVERRIDE_GLIB_MALLOC", 0);
+  metrics_config.validate_allocation_blocks = get_int_from_environment 
("G_METRICS_VALIDATE_ALLOCATION_BLOCKS", 0);
+  metrics_config.use_map_files = get_int_from_environment ("G_METRICS_USE_MAP_FILES", 1);
+}
+
+static void
+load_metrics_logging_config (void)
+{
+  const char *log_dir;
+
+  log_dir = getenv ("G_METRICS_LOG_DIR");
+
+  if (log_dir != NULL)
+    {
+      strncpy (metrics_config.log_dir, log_dir, sizeof (metrics_config.log_dir) - 1);
+    }
+  else
+    {
+      const char *cache_dir;
+      char process_id[32] = "";
+
+      cache_dir = getenv ("XDG_CACHE_HOME");
+
+      if (cache_dir != NULL)
+        {
+          strncat (metrics_config.log_dir, cache_dir, sizeof (metrics_config.log_dir));
+        }
+      else
+        {
+          strncat (metrics_config.log_dir, getenv ("HOME"), sizeof (metrics_config.log_dir));
+          strncat (metrics_config.log_dir, "/.cache", sizeof (metrics_config.log_dir));
+        }
+      strncat (metrics_config.log_dir, "/metrics/", sizeof (metrics_config.log_dir));
+
+       int_to_string (getpid (), process_id, sizeof (process_id));
+
+      strncat (metrics_config.log_dir, process_id, sizeof (metrics_config.log_dir));
+    }
+}
+
+static void
+load_metrics_inclusions_config (void)
+{
+  const char *included_metrics;
+
+  included_metrics = getenv ("G_METRICS_INCLUDE");
+
+  if (included_metrics != NULL)
+    strncpy (metrics_config.included_metrics,
+             included_metrics,
+             sizeof (metrics_config.included_metrics) - 1);
+}
+
+static void
+load_metrics_exclusions_config (void)
+{
+  const char *skipped_metrics;
+
+  skipped_metrics = getenv ("G_METRICS_SKIP");
+
+  if (skipped_metrics != NULL)
+    strncpy (metrics_config.skipped_metrics,
+             skipped_metrics,
+             sizeof (metrics_config.skipped_metrics) - 1);
+  else
+    strncpy (metrics_config.skipped_metrics,
+             default_skipped_metrics,
+             sizeof (metrics_config.skipped_metrics) - 1);
+}
+
+static void
+load_metrics_collection_config (void)
+{
+  const char *collection_ignore_list, *collection_include_list;
+
+  metrics_config.collection_interval = get_int_from_environment ("G_METRICS_COLLECTION_INTERVAL", 10);
+  metrics_config.generations_to_settle = get_int_from_environment 
("G_METRICS_COLLECTION_NUMBER_OF_PRELOAD_INVERVALS", 10);
+  metrics_config.generations_to_reset_average_window = get_int_from_environment 
("G_METRICS_COLLECTION_AVERAGE_WINDOW_THRESHOLD", 10);
+  metrics_config.number_of_interesting_instances = get_int_from_environment 
("G_METRICS_COLLECTION_INSTANCE_COUNT", 10);
+  metrics_config.stack_trace_sample_interval = get_int_from_environment 
("G_METRICS_STACK_TRACE_SAMPLE_INTERVAL", 1);
+
+  collection_ignore_list = getenv ("G_METRICS_COLLECTION_INSTANCE_IGNORE_LIST");
+
+  if (collection_ignore_list != NULL)
+    strncpy (metrics_config.collection_ignore_list,
+             collection_ignore_list,
+             sizeof (metrics_config.collection_ignore_list) - 1);
+  else
+    strncpy (metrics_config.collection_ignore_list,
+             default_collection_ignore_list,
+             sizeof (metrics_config.collection_ignore_list) - 1);
+
+  collection_include_list = getenv ("G_METRICS_COLLECTION_INSTANCE_INCLUDE_LIST");
+
+  if (collection_include_list != NULL)
+    strncpy (metrics_config.collection_include_list,
+             collection_include_list,
+             sizeof (metrics_config.collection_include_list) - 1);
+  else
+    strncpy (metrics_config.collection_include_list,
+             default_collection_ignore_list,
+             sizeof (metrics_config.collection_include_list) - 1);
+}
+
+static void
+load_metrics_stack_trace_config (void)
+{
+  metrics_config.stack_trace_size = get_int_from_environment ("G_METRICS_STACK_TRACE_SIZE", 15);
+
+  metrics_config.stack_trace_annotation_size = get_int_from_environment 
("G_METRICS_STACK_TRACE_ANNOTATION_SIZE", 512);
+}
+
+static void
+load_metrics_config (void)
+{
+  load_metrics_config_command ();
+
+  if (!metrics_config.metrics_enabled)
+    return;
+
+  load_metrics_allocation_config ();
+  load_metrics_logging_config ();
+  load_metrics_inclusions_config ();
+  load_metrics_exclusions_config ();
+  load_metrics_collection_config ();
+  load_metrics_stack_trace_config ();
+}
+
+void
+g_metrics_init (void)
+{
+  static gboolean initialized = 0;
+
+  if (initialized)
+    return;
+
+  load_metrics_config ();
+
+  if (!g_metrics_enabled ())
+    {
+      initialized = TRUE;
+      return;
+    }
+
+  initialize_store_for_allocation_block_stores ();
+  allocate_metrics_block_store ();
+  allocate_thread_default_block_store ();
+
+  initialized = TRUE;
+
+  G_LOCK (timeouts);
+  if (timeout_handlers == NULL)
+    timeout_handlers = g_metrics_list_new ();
+  G_UNLOCK (timeouts);
+}
+
+static void
+g_metrics_allocation_blocks_iter_init_at_block (GMetricsAllocationBlocksIter *iter,
+                                                GMetricsAllocationFileMap    *file_map,
+                                                GMetricsAllocationBlock      *block)
+{
+
+  if (metrics_config.validate_allocation_blocks && block != NULL)
+    {
+        if (block < &file_map->blocks[0])
+            G_BREAKPOINT ();
+
+        if (block >= &file_map->blocks[file_map->number_of_blocks])
+            G_BREAKPOINT ();
+    }
+
+  iter->file_map = file_map;
+
+  if (block != NULL)
+    iter->starting_block = block;
+  else
+    iter->starting_block = &file_map->blocks[0];
+
+  iter->previous_block = NULL;
+  iter->items_examined = 0;
+}
+
+static void
+g_metrics_allocation_blocks_iter_init_after_block (GMetricsAllocationBlocksIter  *iter,
+                                                   GMetricsAllocationFileMap     *file_map,
+                                                   GMetricsAllocationBlock       *block)
+{
+  gsize index = 0;
+  iter->file_map = file_map;
+
+  if (block != NULL)
+    {
+      GMetricsAllocation *allocation;
+      GMetricsAllocationHeader *header;
+
+      if (metrics_config.validate_allocation_blocks)
+        {
+          if (block < &file_map->blocks[0])
+              G_BREAKPOINT ();
+
+          if (block >= &file_map->blocks[file_map->number_of_blocks])
+              G_BREAKPOINT ();
+       }
+
+      allocation = (GMetricsAllocation *) block;
+      header = (GMetricsAllocationHeader *) &allocation->header_block.header;
+
+      index = g_metrics_allocation_file_map_get_index_of_block (file_map, block);
+      index += header->number_of_blocks;
+      index %= file_map->number_of_blocks;
+    }
+
+  iter->starting_block = &file_map->blocks[index];
+
+  iter->previous_block = NULL;
+  iter->items_examined = 0;
+}
+
+static gboolean
+g_metrics_allocation_blocks_iter_next (GMetricsAllocationBlocksIter  *iter,
+                                       GMetricsAllocationBlock      **next_block)
+{
+  GMetricsAllocationFileMap *file_map;
+  GMetricsAllocationBlock *block;
+
+  file_map = iter->file_map;
+
+  if (iter->previous_block == NULL)
+    {
+      block = iter->starting_block;
+    }
+  else
+    {
+      gsize index;
+      GMetricsAllocation *previous_allocation;
+      GMetricsAllocationHeader *previous_header;
+
+      previous_allocation = (GMetricsAllocation *) iter->previous_block;
+      previous_header = &previous_allocation->header_block.header;
+
+      index = g_metrics_allocation_file_map_get_index_of_block (file_map, iter->previous_block);
+      index += previous_header->number_of_blocks;
+      index %= file_map->number_of_blocks;
+
+      block = &file_map->blocks[index];
+    }
+  if (block == iter->starting_block && iter->items_examined > 1)
+    {
+      if (next_block)
+        *next_block = NULL;
+      return FALSE;
+    }
+
+  if (next_block)
+    *next_block = block;
+
+  iter->items_examined++;
+  iter->previous_block = block;
+
+  return TRUE;
+}
+
+static GMetricsAllocation *
+get_allocation (GMetricsAllocationBlockStore *block_store,
+                gsize                         number_of_blocks,
+                const char                   *name)
+{
+  GMetricsAllocationFileMap *file_map;
+  GMetricsAllocationBlocksIter iter;
+  GMetricsAllocationBlock *block;
+  GMetricsAllocation *allocation = NULL;
+
+  file_map = &block_store->file_map;
+  g_metrics_allocation_blocks_iter_init_at_block (&iter, file_map, file_map->large_free_block);
+  while (g_metrics_allocation_blocks_iter_next (&iter, &block))
+    {
+      GMetricsAllocationHeader *header;
+
+      if (metrics_config.validate_allocation_blocks)
+        {
+          if (block >= &file_map->blocks[file_map->number_of_blocks])
+            G_BREAKPOINT ();
+        }
+
+      allocation = (GMetricsAllocation *) block;
+      header = &allocation->header_block.header;
+
+      if (!header->is_freed)
+        continue;
+
+      consolidate_consecutive_blocks (file_map, block, number_of_blocks);
+
+      if (header->number_of_blocks < number_of_blocks)
+        {
+          if (file_map->large_free_block == NULL
+              || file_map->large_free_block->header.number_of_blocks < header->number_of_blocks)
+            file_map->large_free_block = block;
+
+          g_metrics_allocation_file_map_validate_block (file_map, block);
+
+          continue;
+        }
+
+      g_metrics_allocation_file_map_claim_allocation (file_map, allocation);
+      if (header->number_of_blocks > number_of_blocks)
+        g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, number_of_blocks);
+
+      break;
+    }
+
+  g_metrics_allocation_file_map_validate_block (file_map, block);
+
+  allocation = (GMetricsAllocation *) block;
+
+  if (allocation != NULL)
+    {
+      GMetricsAllocationHeader *header;
+
+      header = &allocation->header_block.header;
+
+      if (header->number_of_blocks < number_of_blocks)
+        G_BREAKPOINT ();
+
+      if (name != NULL)
+        strncpy (header->name, name, sizeof (header->name) - 1); header->name[sizeof (header->name) - 1] = 
'\0';
+    }
+  else
+    {
+      G_BREAKPOINT();
+    }
+  return allocation;
+}
+
+static gsize
+calculate_blocks_needed_for_size (gsize size)
+{
+  GMetricsAllocation allocation;
+  gsize aligned_size;
+  static const gsize payload_block_size = sizeof (GMetricsAllocationBlock);
+
+  aligned_size = sizeof (allocation.header_block) + round_to_multiple (size, payload_block_size);
+
+  return aligned_size / payload_block_size;
+}
+
+static GMetricsAllocationBlockStore *
+g_metrics_get_thread_default_allocation_block_store (void)
+{
+  GMetricsAllocationBlockStore *block_store;
+
+  if (!g_metrics_enabled ())
+    return NULL;
+
+  block_store = g_metrics_list_get_last_item (&block_store_stack);
+
+  if (block_store == NULL)
+    {
+      allocate_thread_default_block_store ();
+      block_store = g_metrics_list_get_last_item (&block_store_stack);
+
+      if (block_store != NULL)
+        block_store->stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+    }
+
+  return block_store;
+}
+
+static GMetricsAllocationBlockStore *
+g_metrics_get_allocation_block_store_for_address (gpointer allocation)
+{
+  GMetricsAllocationBlockStoreIter iter;
+  GMetricsAllocationBlockStore *block_store = NULL;
+
+  g_metrics_allocation_block_store_iter_init (&iter);
+  while (g_metrics_allocation_block_store_iter_next (&iter, &block_store))
+    {
+      if (!g_metrics_allocation_block_store_has_allocation (block_store, allocation))
+        continue;
+
+      break;
+    }
+
+  return block_store;
+}
+
+void
+g_metrics_push_default_allocation_block_store (GMetricsAllocationBlockStore *block_store)
+{
+  g_metrics_list_add_item (&block_store_stack, block_store);
+}
+
+void
+g_metrics_pop_default_allocation_block_store (void)
+{
+  g_metrics_list_remove_last_item (&block_store_stack);
+}
+
+gpointer
+g_metrics_allocation_block_store_allocate (GMetricsAllocationBlockStore *block_store,
+                                           gsize                         size)
+{
+  return g_metrics_allocation_block_store_allocate_with_name (block_store, size, NULL);
+}
+
+gpointer
+g_metrics_allocation_block_store_allocate_with_name (GMetricsAllocationBlockStore *block_store,
+                                                     gsize                         size,
+                                                     const char                   *name)
+{
+  GMetricsAllocation *allocation;
+  gsize needed_blocks;
+
+  if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+    return NULL;
+
+  needed_blocks = calculate_blocks_needed_for_size (size);
+
+  G_LOCK (allocations);
+  allocation = get_allocation (block_store, needed_blocks, name);
+  G_UNLOCK (allocations);
+
+  memset (allocation->payload_blocks, 0, size);
+  return (gpointer) allocation->payload_blocks;
+}
+
+gpointer
+g_metrics_allocation_block_store_reallocate (GMetricsAllocationBlockStore *block_store,
+                                             gpointer                      payload,
+                                             gsize                         size)
+{
+  GMetricsAllocationFileMap *file_map;
+  GMetricsAllocationBlock *payload_blocks;
+  GMetricsAllocationBlock *first_block;
+  GMetricsAllocation *allocation;
+  GMetricsAllocationHeader *header;
+  gsize old_size;
+  gsize needed_blocks;
+  gpointer new_payload;
+  gboolean could_grow;
+
+  g_metrics_init ();
+
+  if (!g_metrics_enabled ())
+    return __libc_realloc (payload, size);
+
+  if (size == 0)
+    {
+      g_metrics_allocation_block_store_deallocate (block_store, payload);
+
+      return NULL;
+    }
+
+  if (payload == NULL)
+    return g_metrics_allocation_block_store_allocate_with_name (block_store, size, __func__);
+
+  G_LOCK (allocations);
+  file_map = &block_store->file_map;
+  payload_blocks = (GMetricsAllocationBlock *) payload;
+  first_block = payload_blocks - 1;
+  allocation = (GMetricsAllocation *) first_block;
+  header = &allocation->header_block.header;
+  needed_blocks = calculate_blocks_needed_for_size (size);
+
+  if (needed_blocks == header->number_of_blocks)
+    {
+      G_UNLOCK (allocations);
+      return payload;
+    }
+
+  if (needed_blocks < header->number_of_blocks)
+    {
+      g_metrics_allocation_file_map_shrink_allocation (file_map, allocation, needed_blocks);
+      G_UNLOCK (allocations);
+
+      return payload;
+    }
+
+  could_grow = g_metrics_allocation_file_map_grow_allocation (file_map, allocation, needed_blocks);
+
+  G_UNLOCK (allocations);
+
+  if (could_grow)
+    return payload;
+
+  old_size = g_metrics_allocation_get_payload_size (allocation);
+
+  new_payload = g_metrics_allocation_block_store_allocate_with_name (block_store, size, header->name);
+
+  memcpy (new_payload, payload, old_size);
+
+  g_metrics_allocation_block_store_deallocate (block_store, payload);
+
+  return new_payload;
+}
+
+gpointer
+g_metrics_allocation_block_store_copy (GMetricsAllocationBlockStore *block_store,
+                                       gconstpointer                 allocation,
+                                       gsize                         size)
+{
+  return g_metrics_allocation_block_store_copy_with_name (block_store, allocation, size, __func__);
+}
+
+gpointer
+g_metrics_allocation_block_store_copy_with_name (GMetricsAllocationBlockStore *block_store,
+                                                 gconstpointer                 allocation,
+                                                 gsize                         size,
+                                                 const char                   *name)
+{
+  gpointer copy;
+
+  copy = g_metrics_allocation_block_store_allocate_with_name (block_store, size, name);
+
+  memcpy (copy, allocation, size);
+
+  return copy;
+}
+
+__attribute__((visibility("default")))
+void *
+malloc (size_t size)
+{
+  if (!metrics_config.override_system_malloc)
+    return __libc_malloc (size);
+
+  return g_metrics_allocate (size);
+}
+
+__attribute__((visibility("default")))
+void *
+calloc (size_t nmemb, size_t size)
+{
+  if (!metrics_config.override_system_malloc)
+    return __libc_calloc (nmemb, size);
+
+  return g_metrics_allocate (size * nmemb);
+}
+
+__attribute__((visibility("default")))
+void *realloc (void *ptr, size_t size)
+{
+  if (!metrics_config.override_system_malloc)
+    return __libc_realloc (ptr, size);
+
+  return g_metrics_reallocate (ptr, size);
+}
+
+__attribute__((visibility("default")))
+void
+free (void *ptr)
+{
+  g_metrics_free (ptr);
+}
+
+void
+g_metrics_allocation_block_store_deallocate (GMetricsAllocationBlockStore *block_store,
+                                             gpointer                      payload)
+{
+  GMetricsAllocationBlock *payload_blocks;
+  GMetricsAllocationBlock *first_block;
+  GMetricsAllocation *allocation;
+
+  if (!payload)
+    return;
+
+  G_LOCK (allocations);
+  payload_blocks = (GMetricsAllocationBlock *) payload;
+  first_block = payload_blocks - 1;
+
+  allocation = (GMetricsAllocation *) first_block;
+
+  g_metrics_allocation_file_map_release_allocation (&block_store->file_map, allocation);
+  G_UNLOCK (allocations);
+
+  if (block_store->total_bytes_allocated == 0 && block_store->is_dedicated)
+    g_metrics_allocation_block_store_free (block_store);
+}
+
+gpointer
+g_metrics_allocate (gsize size)
+{
+  GMetricsAllocationBlockStore *block_store = NULL;
+  static gsize counter = 0;
+
+  g_metrics_init ();
+
+  if (!metrics_config.override_glib_malloc)
+    goto fallback;
+
+  block_store = g_metrics_get_thread_default_allocation_block_store ();
+  if (block_store == NULL)
+    goto fallback;
+
+  if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+    goto fallback;
+
+  if (size >= metrics_config.dedicated_allocation_block_store_threshold)
+    {
+      GMetricsAllocationBlockStore *dedicated_block_store = NULL;
+      char *name;
+
+      asprintf (&name, "allocation-%ld-%ld", size, counter);
+      counter++;
+
+      dedicated_block_store = g_metrics_allocation_block_store_new (name, 
metrics_config.allocation_block_store_size);
+      g_metrics_free (name);
+
+      if (dedicated_block_store != NULL)
+        {
+          dedicated_block_store->is_dedicated = TRUE;
+          dedicated_block_store->stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+          return g_metrics_allocation_block_store_allocate (dedicated_block_store, size);
+        }
+    }
+
+  return g_metrics_allocation_block_store_allocate (block_store, size);
+
+fallback:
+  return __libc_calloc (1, size);
+}
+
+gpointer
+g_metrics_reallocate (gpointer allocation,
+                      gsize    size)
+{
+  GMetricsAllocationBlockStore *block_store = NULL;
+
+  g_metrics_init ();
+
+  if (!metrics_config.override_glib_malloc)
+    goto fallback;
+
+  block_store = g_metrics_get_allocation_block_store_for_address (allocation);
+  if (block_store == NULL)
+    goto fallback;
+
+  if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+    goto fallback;
+
+  return g_metrics_allocation_block_store_reallocate (block_store, allocation, size);
+
+fallback:
+  return __libc_realloc (allocation, size);
+}
+
+gpointer
+g_metrics_copy (gconstpointer allocation,
+                gsize         size)
+{
+  GMetricsAllocationBlockStore *block_store = NULL;
+  gpointer copy;
+
+  g_metrics_init ();
+
+  if (!metrics_config.override_glib_malloc)
+    goto fallback;
+
+  block_store = g_metrics_get_thread_default_allocation_block_store ();
+  if (block_store == NULL)
+    goto fallback;
+
+  if (!g_metrics_allocation_file_map_is_open (&block_store->file_map))
+    goto fallback;
+
+  return g_metrics_allocation_block_store_copy_with_name (block_store, allocation, size, __func__);
+
+fallback:
+  copy = __libc_malloc (size);
+  memcpy (copy, allocation, size);
+  return copy;
+}
+
+void
+g_metrics_free (gpointer allocation)
+{
+  GMetricsAllocationBlockStore *block_store = NULL;
+
+  if (!allocation)
+    return;
+
+  block_store = g_metrics_get_allocation_block_store_for_address (allocation);
+
+  if (block_store != NULL)
+    {
+      g_metrics_allocation_block_store_deallocate (block_store, allocation);
+      return;
+    }
+
+  __libc_free (allocation);
+}
+
+static void
+g_metrics_file_write (GMetricsFile *metrics_file,
+                      const char   *data,
+                      gsize         size)
+{
+  gchar *buf = (gchar *) data;
+  gsize to_write = size;
+
+  while (to_write > 0)
+    {
+      gssize count = gzwrite (metrics_file->gzipped_file, buf, to_write);
+      if (count <= 0)
+        {
+          if (errno != EINTR)
+            return;
+        }
+      else
+        {
+          to_write -= count;
+          buf += count;
+        }
+    }
+}
+
+static void
+on_sigusr1 (int signal_number)
+{
+  needs_flush = TRUE;
+}
+
+GMetricsFile *
+g_metrics_file_new (const char *name,
+                    const char *first_column_name,
+                    const char *first_column_format,
+                    ...)
+{
+  GMetricsFile *metrics_file;
+  va_list args;
+  const char *column_name, *column_format;
+  UT_string *format_string = NULL;
+  UT_string *header_string = NULL;
+  char *filename = NULL;
+
+  g_metrics_init ();
+
+  utstring_new (format_string);
+  utstring_new (header_string);
+  utstring_printf (header_string, "generation,timestamp,%s", first_column_name);
+
+  va_start (args, first_column_format);
+  do
+    {
+      column_name = va_arg (args, const char *);
+
+      if (column_name == NULL)
+        break;
+
+      column_format = va_arg (args, const char *);
+
+      utstring_printf (header_string, ",%s", column_name);
+      utstring_printf (format_string, ",%s", column_format);
+    }
+  while (column_name != NULL);
+  va_end (args);
+
+  utstring_printf (header_string, "\n");
+
+  metrics_file = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store,
+                                                                      sizeof (GMetricsFile),
+                                                                      "GMetricsFile");
+  memset (metrics_file, 0, sizeof (GMetricsFile));
+
+  asprintf (&metrics_file->static_format_string, "%%lu,%%lf,%s", first_column_format);
+  metrics_file->variadic_format_string = strdup (utstring_body (format_string));
+
+  asprintf (&filename,"%s/%s.csv.gz", metrics_config.log_dir, name);
+  g_mkdir_with_parents (metrics_config.log_dir, 0755);
+
+  metrics_file->gzipped_file = gzopen (filename, "wbe");
+  g_metrics_file_write (metrics_file, utstring_body (header_string), utstring_len (header_string));
+  utstring_free (format_string);
+  utstring_free (header_string);
+  free (filename);
+
+  signal (SIGUSR1, on_sigusr1);
+
+  return metrics_file;
+}
+
+void
+g_metrics_file_start_record (GMetricsFile *metrics_file)
+{
+  metrics_file->now = ((long double) g_get_real_time ()) / (1.0 * G_USEC_PER_SEC);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+void
+g_metrics_file_add_row_without_cast (GMetricsFile  *metrics_file,
+                                     gconstpointer  first_column_value,
+                                     ...)
+
+{
+  va_list args;
+  gsize row_length = 0, buffer_left = 0, buffer_written = 0;
+  char *row;
+  gulong generation;
+
+  generation = g_metrics_get_generation ();
+
+  row_length += snprintf (NULL, 0, metrics_file->static_format_string, generation, metrics_file->now, 
first_column_value);
+
+  va_start (args, first_column_value);
+  row_length += vsnprintf (NULL, 0, metrics_file->variadic_format_string, args);
+  va_end (args);
+
+  row_length++;
+
+  buffer_left = row_length + 1;
+  row = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, buffer_left, 
__func__);
+
+  buffer_written += snprintf (row, buffer_left, metrics_file->static_format_string, generation, 
metrics_file->now, first_column_value);
+  buffer_left -= buffer_written;
+
+  va_start (args, first_column_value);
+  buffer_written += vsnprintf (row + buffer_written, buffer_left, metrics_file->variadic_format_string, 
args);
+  buffer_left -= buffer_written;
+  va_end (args);
+  *(row + buffer_written) = '\n';
+
+  g_metrics_file_write (metrics_file, row, row_length);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, row);
+}
+#pragma GCC diagnostic pop
+
+void
+g_metrics_file_end_record (GMetricsFile *metrics_file)
+{
+  gulong generation;
+
+  generation = g_metrics_get_generation ();
+
+  if (needs_flush)
+    {
+      gzflush (metrics_file->gzipped_file, Z_FULL_FLUSH);
+    }
+  else if ((generation % 10) == 0)
+    {
+      gzflush (metrics_file->gzipped_file, Z_PARTIAL_FLUSH);
+    }
+}
+
+void
+g_metrics_file_free (GMetricsFile *metrics_file)
+{
+  gzclose (metrics_file->gzipped_file);
+  free (metrics_file->static_format_string);
+  free (metrics_file->variadic_format_string);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, metrics_file);
+}
+
+struct _GMetricsTableEntry
+{
+  UT_hash_handle hh;
+  char *name;
+  gpointer record;
+};
+
+struct _GMetricsTable
+{
+  gsize record_size;
+  GMetricsTableEntry *entries;
+  gboolean is_sorted;
+};
+
+GMetricsTable *
+g_metrics_table_new (gsize record_size)
+{
+  GMetricsTable *table;
+
+  g_metrics_init ();
+
+  table = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsTable), "GMetricsTable");
+  table->record_size = record_size;
+
+  return table;
+}
+
+void
+g_metrics_table_set_record (GMetricsTable *metrics_table,
+                            const char    *name,
+                            gpointer       record)
+{
+  GMetricsTableEntry *entry = NULL;
+
+  HASH_FIND_STR (metrics_table->entries, name, entry);
+
+  if (entry == NULL)
+    {
+      entry = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsTableEntry), "GMetricsTableEntry");
+      memset (entry, 0, sizeof (GMetricsTableEntry));
+
+      entry->name = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, name, 
strlen (name) + 1, "GMetricsTableEntry::name");
+      entry->record = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, 
record, metrics_table->record_size, "GMetricsTableEntry::record");
+
+      HASH_ADD_KEYPTR (hh, metrics_table->entries, entry->name, strlen (entry->name), entry);
+    }
+  else
+    {
+      gpointer old_record;
+      old_record = entry->record;
+      entry->record = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, 
record, metrics_table->record_size, "GMetricsTableEntry::record");
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, old_record);
+    }
+  metrics_table->is_sorted = FALSE;
+}
+
+gpointer
+g_metrics_table_get_record (GMetricsTable *metrics_table,
+                            const char    *name)
+{
+  GMetricsTableEntry *entry = NULL;
+
+  HASH_FIND_STR (metrics_table->entries, name, entry);
+
+  if (entry == NULL)
+    return NULL;
+
+  return entry->record;
+}
+
+void
+g_metrics_table_remove_record (GMetricsTable *metrics_table,
+                               const char    *name)
+{
+  GMetricsTableEntry *entry = NULL;
+
+  HASH_FIND_STR (metrics_table->entries, name, entry);
+
+  if (entry == NULL)
+    return;
+
+  HASH_DEL (metrics_table->entries, entry);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->name);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->record);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry);
+
+  metrics_table->is_sorted = FALSE;
+}
+
+void
+g_metrics_table_clear (GMetricsTable *metrics_table)
+{
+  GMetricsTableEntry *entry = NULL, *next_entry = NULL;
+
+  HASH_ITER (hh, metrics_table->entries, entry, next_entry)
+    {
+      HASH_DEL (metrics_table->entries, entry);
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->name);
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry->record);
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, entry);
+    }
+  metrics_table->is_sorted = FALSE;
+}
+
+void
+g_metrics_table_free (GMetricsTable *metrics_table)
+{
+  g_metrics_table_clear (metrics_table);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, metrics_table);
+}
+
+void
+g_metrics_table_iter_init (GMetricsTableIter *iter,
+                           GMetricsTable     *table)
+{
+  iter->entry = table->entries;
+}
+
+static int
+comparison_wrapper (GMetricsTableEntry *entry_1,
+                    GMetricsTableEntry *entry_2,
+                    GCompareFunc        comparison_function)
+{
+  if (comparison_function == NULL)
+    return g_strcmp0 (entry_1->name, entry_2->name);
+
+  return comparison_function (entry_1->record, entry_2->record);
+}
+
+void
+g_metrics_table_sorted_iter_init (GMetricsTableIter *iter,
+                                  GMetricsTable     *table,
+                                  GCompareFunc       comparison_function)
+{
+  if (!table->is_sorted)
+    {
+      HASH_SRT_DATA (hh, table->entries, comparison_wrapper, comparison_function);
+      table->is_sorted = TRUE;
+    }
+
+  iter->entry = table->entries;
+}
+
+gboolean
+g_metrics_table_iter_next_without_cast (GMetricsTableIter  *iter,
+                                        const char        **name,
+                                        gpointer           *record)
+{
+  if (iter->entry == NULL)
+    {
+      if (name)
+        *name = NULL;
+      return FALSE;
+    }
+
+  if (name)
+    *name = (const char *) iter->entry->name;
+
+  if (iter->entry->record == NULL)
+    G_BREAKPOINT ();
+
+  if (record)
+    *record = iter->entry->record;
+
+  iter->entry = iter->entry->hh.next;
+  return TRUE;
+}
+
+struct _GMetricsInstanceCounterEntry
+{
+  UT_hash_handle hh;
+  char *name;
+  gpointer record;
+};
+
+struct _GMetricsInstanceCounter
+{
+  GMetricsTable *tables[2];
+  GMetricsTable *interesting_instances;
+  ssize_t last_table_index;
+  ssize_t current_table_index;
+  size_t  generation;
+};
+
+static gint
+g_metrics_instance_counter_metrics_sort (GMetricsInstanceCounterMetrics *a, GMetricsInstanceCounterMetrics 
*b)
+{
+  if (b->average_instance_change == a->average_instance_change)
+    {
+      if (b->total_memory_usage == a->total_memory_usage)
+        {
+          if (b->instance_count == a->instance_count)
+            return 0;
+
+          if (b->instance_count > a->instance_count)
+            return 1;
+
+          return -1;
+        }
+
+      if (b->total_memory_usage > a->total_memory_usage)
+        return 1;
+
+      return -1;
+    }
+
+  if (b->average_instance_change > a->average_instance_change)
+    return 1;
+
+  return -1;
+}
+
+GMetricsInstanceCounter *
+g_metrics_instance_counter_new (void)
+{
+  GMetricsInstanceCounter *counter;
+
+  g_metrics_init ();
+
+  counter = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store,
+                                                                 sizeof (GMetricsInstanceCounter),
+                                                                 "GMetricsInstanceCounter");
+  memset (counter, 0, sizeof (GMetricsInstanceCounter));
+  counter->last_table_index = -1;
+  counter->interesting_instances = g_metrics_table_new (sizeof (gboolean));
+
+  return counter;
+}
+
+void
+g_metrics_instance_counter_start_record (GMetricsInstanceCounter *counter)
+{
+  GMetricsTable *table;
+
+  counter->current_table_index = (counter->last_table_index + 1) % G_N_ELEMENTS (counter->tables);
+
+  if (counter->tables[counter->current_table_index] == NULL)
+    counter->tables[counter->current_table_index] = g_metrics_table_new (sizeof 
(GMetricsInstanceCounterMetrics));
+
+  table = counter->tables[counter->current_table_index];
+  g_metrics_table_clear (table);
+}
+
+void
+g_metrics_instance_counter_end_record (GMetricsInstanceCounter *counter)
+{
+  GMetricsTable *old_table, *table;
+
+  old_table = counter->tables[counter->last_table_index];
+  table = counter->tables[counter->current_table_index];
+
+  if (old_table != NULL)
+    {
+      GMetricsInstanceCounterMetrics *old_metrics = NULL;
+      const char *name = NULL;
+      GMetricsTableIter metrics_iter;
+
+      g_metrics_table_iter_init (&metrics_iter, old_table);
+      while (g_metrics_table_iter_next (&metrics_iter, &name, &old_metrics))
+        {
+            GMetricsInstanceCounterMetrics *metrics;
+
+            metrics = g_metrics_table_get_record (table, name);
+            if (metrics == NULL)
+            {
+                GMetricsInstanceCounterMetrics new_metrics = { { 0 }, 0 };
+                new_metrics.instance_change = -old_metrics->instance_count;
+                g_metrics_table_set_record (table, name, &new_metrics);
+            }
+        }
+      g_metrics_table_clear (old_table);
+    }
+
+  counter->last_table_index = counter->current_table_index;
+  counter->current_table_index = -1;
+}
+
+gboolean
+g_metrics_instance_counter_instance_is_interesting (GMetricsInstanceCounter *counter,
+                                                    const char              *name)
+{
+  GMetricsInstanceCounterIter iter;
+  GMetricsInstanceCounterMetrics *metrics;
+  const char *instance_name;
+  size_t i = 0;
+
+  if (strstr (name, metrics_config.collection_include_list) != NULL)
+    return TRUE;
+
+  g_metrics_instance_counter_iter_init (&iter, counter);
+  while (g_metrics_instance_counter_iter_next (&iter, &instance_name, &metrics) &&
+         i < metrics_config.number_of_interesting_instances)
+    {
+      if (g_strcmp0 (name, instance_name) == 0 &&
+          strstr (name, metrics_config.collection_ignore_list) == NULL &&
+          metrics->average_instance_change > 0)
+        return TRUE;
+      i++;
+    }
+
+  return FALSE;
+}
+
+void
+g_metrics_instance_counter_add_instances (GMetricsInstanceCounter *counter,
+                                          const char              *name,
+                                          const char              *comment,
+                                          size_t                   number_of_instances,
+                                          size_t                   total_usage)
+{
+  GMetricsTable *old_table, *table;
+  GMetricsInstanceCounterMetrics *metrics;
+  size_t old_instance_count = 0, old_instance_watermark = 0;
+  ssize_t old_average_instance_change = 0;
+  size_t old_number_of_samples = 0;
+  gulong generation;
+
+  old_table = counter->tables[counter->last_table_index];
+  table = counter->tables[counter->current_table_index];
+
+  if (old_table != NULL)
+    {
+      GMetricsInstanceCounterMetrics *old_metrics = NULL;
+
+      old_metrics = g_metrics_table_get_record (old_table, name);
+
+      if (old_metrics != NULL)
+        {
+          old_average_instance_change = old_metrics->average_instance_change;
+          old_instance_count = old_metrics->instance_count;
+          old_instance_watermark = old_metrics->instance_watermark;
+          old_number_of_samples = old_metrics->number_of_samples;
+        }
+    }
+
+  metrics = g_metrics_table_get_record (table, name);
+  if (metrics == NULL)
+    {
+      GMetricsInstanceCounterMetrics new_metrics = { { 0 }, 0 };
+      g_metrics_table_set_record (table, name, &new_metrics);
+
+      metrics = g_metrics_table_get_record (table, name);
+      if (comment != NULL)
+        strncpy (metrics->comment, comment, sizeof (metrics->comment) - 1);
+    }
+
+  metrics->instance_count += number_of_instances;
+  metrics->instance_change = metrics->instance_count - old_instance_count;
+
+  generation = g_metrics_get_generation ();
+  if (generation > metrics_config.generations_to_settle &&
+      metrics->instance_change != 0)
+    {
+    if (old_number_of_samples != 0)
+      {
+        metrics->average_instance_change = (old_average_instance_change * (ssize_t) old_number_of_samples) + 
(((ssize_t) metrics->instance_change) - old_average_instance_change);
+        metrics->average_instance_change /= (ssize_t) old_number_of_samples;
+        metrics->number_of_samples = old_number_of_samples + 1;
+      }
+    else
+      {
+        metrics->average_instance_change = metrics->instance_change;
+        metrics->number_of_samples = 1;
+      }
+    }
+  else
+    {
+      metrics->average_instance_change = old_average_instance_change;
+      metrics->number_of_samples = old_number_of_samples;
+    }
+
+  metrics->instance_watermark = MAX (metrics->instance_watermark, metrics->instance_count);
+  metrics->instance_watermark = MAX (metrics->instance_watermark, old_instance_watermark);
+  metrics->total_memory_usage += total_usage;
+
+  g_metrics_table_set_record (table, name, metrics);
+}
+
+void
+g_metrics_instance_counter_add_instance (GMetricsInstanceCounter *counter,
+                                         const char              *name,
+                                         size_t                   memory_usage)
+{
+  g_metrics_instance_counter_add_instances (counter,
+                                            name,
+                                            NULL,
+                                            1,
+                                            memory_usage);
+}
+
+void
+g_metrics_instance_counter_free (GMetricsInstanceCounter *counter)
+{
+  size_t i;
+
+  for (i = 0; i < G_N_ELEMENTS (counter->tables); i++)
+    g_metrics_table_free (counter->tables[i]);
+
+  g_metrics_table_free (counter->interesting_instances);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, counter);
+}
+
+void
+g_metrics_instance_counter_iter_init (GMetricsInstanceCounterIter *iter,
+                                      GMetricsInstanceCounter     *counter)
+{
+  GMetricsTable *old_table;
+
+  iter->table_index = counter->last_table_index;
+
+  old_table = counter->tables[iter->table_index];
+
+  if (old_table == NULL)
+    {
+      memset (&iter->table_iter, 0, sizeof (iter->table_iter));
+      return;
+    }
+
+  g_metrics_table_sorted_iter_init (&iter->table_iter, old_table, (GCompareFunc) 
g_metrics_instance_counter_metrics_sort);
+}
+
+gboolean
+g_metrics_instance_counter_iter_next (GMetricsInstanceCounterIter    *iter,
+                                      const char                    **name,
+                                      GMetricsInstanceCounterMetrics **metrics)
+{
+  while (g_metrics_table_iter_next (&iter->table_iter, name, metrics))
+    {
+      if ((*metrics)->instance_change == 0)
+        continue;
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+gboolean
+g_metrics_requested (const char *name)
+{
+  if (!g_metrics_enabled ())
+    return FALSE;
+
+  if (strstr (metrics_config.included_metrics, name) != NULL)
+    return TRUE;
+
+  if (strstr (metrics_config.skipped_metrics, name) != NULL)
+    return FALSE;
+
+  return TRUE;
+}
+
+void
+g_metrics_start_timeout (GMetricsTimeoutFunc timeout_handler)
+{
+  G_LOCK (timeouts);
+
+  if (timeout_fd < 0)
+    {
+      struct itimerspec timer_spec = { { 0 } };
+
+      timer_spec.it_interval.tv_sec = metrics_config.collection_interval;
+      timer_spec.it_value.tv_sec = metrics_config.collection_interval;
+
+      timeout_fd = timerfd_create (CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+      if (timeout_fd >= 0)
+        {
+          int result;
+          result = timerfd_settime (timeout_fd, 0, &timer_spec, NULL);
+
+          if (result < 0)
+            {
+              close (timeout_fd);
+              timeout_fd = -1;
+            }
+        }
+    }
+
+  g_metrics_list_add_item (timeout_handlers, timeout_handler);
+
+  G_UNLOCK (timeouts);
+}
+
+static void
+init_allocation_block_stores_metrics (void)
+{
+  static gboolean initialized;
+  static gboolean needs_allocation_block_store_metrics;
+
+  if (initialized)
+    return;
+
+  initialized = TRUE;
+  needs_allocation_block_store_metrics = g_metrics_requested ("allocation-block-stores");
+
+  if (!needs_allocation_block_store_metrics)
+    return;
+
+  allocation_block_store_metrics_file = g_metrics_file_new ("allocation-block-stores",
+                                                            "name", "%s",
+                                                            "thread name", "%s",
+                                                            "number of allocations", "%ld",
+                                                            "total size", "%ld",
+                                                            "stack trace", "%s",
+                                                            NULL);
+  g_metrics_start_timeout (on_allocation_block_stores_metrics_timeout);
+}
+
+void
+g_metrics_run_timeout_handlers (void)
+{
+  GMetricsListIter iter;
+  GMetricsTimeoutFunc handler;
+
+  guint64 number_of_expirations;
+
+  read (timeout_fd, &number_of_expirations, sizeof (number_of_expirations));
+
+  init_allocation_block_stores_metrics ();
+
+  G_LOCK (timeouts);
+  g_metrics_list_iter_init (&iter, timeout_handlers);
+  while (g_metrics_list_iter_next (&iter, &handler))
+    {
+      if (handler != NULL)
+        handler ();
+    }
+  g_metrics_generation++;
+  G_UNLOCK (timeouts);
+
+  needs_flush = FALSE;
+}
+
+gulong
+g_metrics_get_generation (void)
+{
+  return g_metrics_generation;
+}
+
+struct _GMetricsListNode
+{
+  gpointer item;
+  struct _GMetricsListNode *prev;
+  struct _GMetricsListNode *next;
+};
+
+struct _GMetricsList
+{
+  struct _GMetricsListNode *first_node;
+  gsize number_of_nodes;
+};
+
+GMetricsList *
+g_metrics_list_new (void)
+{
+  GMetricsList *list;
+
+  g_metrics_init ();
+
+  list = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsList), "GMetricsList");
+  memset (list, 0, sizeof (GMetricsList));
+
+  return list;
+}
+
+void
+g_metrics_list_add_item (GMetricsList *list,
+                         gpointer      item)
+{
+  GMetricsListNode *node;
+
+  node = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsListNode), "GMetricsListNode");
+  memset (node, 0, sizeof (GMetricsListNode));
+
+  node->item = item;
+
+  DL_APPEND (list->first_node, node);
+  list->number_of_nodes++;
+}
+
+void
+g_metrics_list_remove_item (GMetricsList *list,
+                            gpointer      item_to_remove)
+{
+  GMetricsListNode *node = NULL;
+
+  DL_SEARCH_SCALAR (list->first_node, node, item, item_to_remove);
+
+  if (node != NULL)
+    {
+      DL_DELETE (list->first_node, node);
+      list->number_of_nodes--;
+
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, node);
+    }
+}
+
+gpointer
+g_metrics_list_get_last_item (GMetricsList *list)
+{
+  GMetricsListNode *last_node = NULL;
+
+  if (list->number_of_nodes == 0)
+    return NULL;
+
+  last_node = list->first_node->prev;
+
+  return last_node->item;
+}
+
+void
+g_metrics_list_remove_last_item (GMetricsList *list)
+{
+  GMetricsListNode *last_node = NULL;
+
+  if (list->number_of_nodes == 0)
+    return;
+
+  last_node = list->first_node->prev;
+
+  DL_DELETE (list->first_node, last_node);
+  list->number_of_nodes--;
+}
+
+gsize
+g_metrics_list_get_length (GMetricsList *list)
+{
+  return list->number_of_nodes;
+}
+
+void
+g_metrics_list_free (GMetricsList *list)
+{
+  GMetricsListNode *node, *next_node;
+
+  DL_FOREACH_SAFE (list->first_node, node, next_node)
+    {
+      DL_DELETE (list->first_node, node);
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, node);
+    }
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, list);
+}
+
+void
+g_metrics_list_iter_init (GMetricsListIter *iter,
+                          GMetricsList     *list)
+{
+  iter->node = list->first_node;
+
+  if (iter->node != NULL)
+    iter->next_node = iter->node->next;
+}
+
+gboolean
+g_metrics_list_iter_next_without_cast (GMetricsListIter *iter,
+                                       gpointer         *item)
+{
+  if (iter->node == NULL)
+    {
+      *item = NULL;
+      return FALSE;
+    }
+
+  *item = iter->node->item;
+
+  iter->node = iter->next_node;
+
+  if (iter->node != NULL)
+    iter->next_node = iter->node->next;
+  return TRUE;
+}
+
+struct _GMetricsStackTrace
+{
+  gpointer *frames;
+  int start_frame;
+  int number_of_frames;
+  char *delimiter;
+  char *output;
+  char *hash_key;
+  char *annotation;
+};
+
+GMetricsStackTrace *
+g_metrics_stack_trace_new (int start_frame,
+                           int number_of_frames,
+                           const char *delimiter)
+{
+  GMetricsStackTrace *stack_trace;
+  int total_frames_needed;
+
+  stack_trace = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsStackTrace), "GMetricsStackTrace");
+  memset (stack_trace, 0, sizeof (GMetricsStackTrace));
+
+  total_frames_needed = start_frame + number_of_frames;
+  stack_trace->frames = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, 
sizeof (gpointer) * (total_frames_needed), "GMetricsStackTrace::frames");
+  stack_trace->number_of_frames = backtrace (stack_trace->frames, total_frames_needed);
+  stack_trace->start_frame = start_frame;
+  stack_trace->delimiter = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, 
delimiter, strlen (delimiter) + 1, "GMetricsStackTrace::delimiter");
+
+  return stack_trace;
+}
+
+const char *
+g_metrics_stack_trace_get_hash_key (GMetricsStackTrace *stack_trace)
+{
+  if (stack_trace->hash_key == NULL)
+    {
+      size_t i;
+      int total_frames_needed;
+      char *end;
+      size_t key_size;
+      size_t annotation_length = 0;
+
+      total_frames_needed = stack_trace->number_of_frames - stack_trace->start_frame;
+      if (stack_trace->annotation != NULL)
+        annotation_length = strlen (stack_trace->annotation);
+      key_size = strlen ("c00ldead0ldc0des") * total_frames_needed + annotation_length + 1;
+      stack_trace->hash_key = g_metrics_allocation_block_store_allocate_with_name 
(metrics_allocation_block_store, key_size, "GMetricsStackTrace::hash_key");
+
+      end = stack_trace->hash_key;
+      for (i = stack_trace->start_frame; i < stack_trace->number_of_frames; i++)
+        {
+          size_t key_size_left;
+
+          key_size_left = key_size - (end - stack_trace->hash_key);
+          end = int_to_string ((uintptr_t)stack_trace->frames[i], end, key_size_left);
+        }
+
+      if (stack_trace->annotation != NULL)
+        memcpy (end, stack_trace->annotation, annotation_length + 1);
+    }
+
+  return stack_trace->hash_key;
+}
+
+const char *
+g_metrics_stack_trace_get_output (GMetricsStackTrace *stack_trace)
+{
+  if (stack_trace->output == NULL)
+    {
+      UT_string *output_string = NULL;
+      char **symbols = NULL;
+      int i;
+
+      symbols = backtrace_symbols (stack_trace->frames, stack_trace->number_of_frames);
+
+      if (symbols == NULL)
+        return NULL;
+
+      utstring_new (output_string);
+      if (stack_trace->annotation != NULL)
+        utstring_printf (output_string, "%s: ", stack_trace->annotation);
+      for (i = stack_trace->start_frame; i < stack_trace->number_of_frames; i++)
+        utstring_printf (output_string, "%s%s", symbols[i], stack_trace->delimiter);
+      stack_trace->output = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, 
utstring_body (output_string), utstring_len (output_string) + 1, "GMetricsStackTrace::output");
+      utstring_free (output_string);
+      g_metrics_free (symbols);
+    }
+
+  return stack_trace->output;
+}
+
+void
+g_metrics_stack_trace_add_annotation (GMetricsStackTrace *stack_trace,
+                                      const char         *annotation)
+{
+  stack_trace->annotation = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, 
annotation, strlen (annotation) + 1, "GMetricsStackTrace::annotation");
+}
+
+void
+g_metrics_stack_trace_free (GMetricsStackTrace *stack_trace)
+{
+  if (stack_trace == NULL)
+    return;
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->annotation);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->hash_key);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->frames);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->output);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace->delimiter);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, stack_trace);
+}
+
+char *
+g_metrics_stack_trace (void)
+{
+  GMetricsStackTrace *stack_trace;
+  const char *output;
+  char *copy = NULL;
+
+  stack_trace = g_metrics_stack_trace_new (2, metrics_config.stack_trace_size, " -> ");
+  output = g_metrics_stack_trace_get_output (stack_trace);
+
+  if (output != NULL)
+    copy = g_metrics_allocation_block_store_copy_with_name (metrics_allocation_block_store, output, strlen 
(output) + 1, __func__);
+
+  return copy;
+}
+
+struct _GMetricsStackTraceSampler
+{
+  GMetricsTable *traces_table;
+  GMetricsTable *instances_table;
+};
+
+typedef struct _GMetricsStackTraceSamplerInstanceEntry GMetricsStackTraceSamplerInstanceEntry;
+struct _GMetricsStackTraceSamplerInstanceEntry
+{
+  gpointer instance;
+  char *trace_hash_key;
+};
+
+static int
+g_metrics_stack_trace_sample_sort (GMetricsStackTraceSample *a,
+                                   GMetricsStackTraceSample *b)
+{
+  if (b->number_of_hits == a->number_of_hits)
+    return g_strcmp0 (a->name, b->name);
+
+  if (b->number_of_hits > a->number_of_hits)
+    return 1;
+
+  return -1;
+}
+
+void
+g_metrics_stack_trace_sampler_iter_init (GMetricsStackTraceSamplerIter *iter,
+                                         GMetricsStackTraceSampler     *sampler)
+{
+  g_metrics_table_sorted_iter_init (&iter->table_iter, sampler->traces_table, (GCompareFunc) 
g_metrics_stack_trace_sample_sort);
+}
+
+gboolean
+g_metrics_stack_trace_sampler_iter_next (GMetricsStackTraceSamplerIter   *iter,
+                                         GMetricsStackTraceSample       **sample)
+{
+  const char *key;
+
+  if (!g_metrics_table_iter_next (&iter->table_iter, &key, sample))
+    return FALSE;
+
+  return TRUE;
+}
+
+void
+g_metrics_set_stack_trace_annotation_handler (GMetricsStackTraceAnnotationHandler   handler,
+                                              gpointer                              user_data)
+{
+  stack_trace_annotation_handler = handler;
+  stack_trace_annotation_handler_user_data = user_data;
+}
+
+GMetricsStackTraceSampler *
+g_metrics_stack_trace_sampler_new (void)
+{
+  GMetricsStackTraceSampler *sampler;
+
+  sampler = g_metrics_allocation_block_store_allocate_with_name (metrics_allocation_block_store, sizeof 
(GMetricsStackTraceSampler), "GMetricsStackTraceSampler");
+  memset (sampler, 0, sizeof (GMetricsStackTraceSampler));
+
+  sampler->traces_table = g_metrics_table_new (sizeof (GMetricsStackTraceSample));
+  sampler->instances_table = g_metrics_table_new (sizeof (GMetricsStackTraceSamplerInstanceEntry));
+
+  return sampler;
+}
+
+void
+g_metrics_stack_trace_sampler_take_sample (GMetricsStackTraceSampler *sampler,
+                                           const char                *name,
+                                           gpointer                   instance)
+{
+  GMetricsStackTrace *stack_trace;
+  GMetricsStackTraceSample *sample;
+  GMetricsStackTraceSamplerInstanceEntry instance_entry = { 0 };
+  const char *trace_key;
+  char instance_name[64] = "";
+
+  if ((g_random_int () % metrics_config.stack_trace_sample_interval) != 0)
+    return;
+
+  stack_trace = g_metrics_stack_trace_new (4, 5, " -> ");
+
+  if (stack_trace_annotation_handler != NULL)
+    {
+      char annotation[metrics_config.stack_trace_annotation_size];
+      gboolean annotated = FALSE;
+
+      annotation[0] = '\0';
+      annotated = stack_trace_annotation_handler(annotation, metrics_config.stack_trace_annotation_size - 1, 
stack_trace_annotation_handler_user_data);
+      if (annotated)
+        {
+          annotation[metrics_config.stack_trace_annotation_size - 1] = '\0';
+          g_metrics_stack_trace_add_annotation (stack_trace, annotation);
+        }
+    }
+
+  trace_key = g_metrics_stack_trace_get_hash_key (stack_trace);
+
+  sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+
+  if (sample == NULL)
+    {
+      GMetricsStackTraceSample empty_sample = { { 0 }, 0 };
+
+      g_metrics_table_set_record (sampler->traces_table, trace_key, &empty_sample);
+
+      sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+      sample->stack_trace = stack_trace;
+      strncpy (sample->name, name, sizeof (sample->name) - 1);
+      stack_trace = NULL;
+    }
+
+  sample->number_of_hits++;
+  g_metrics_stack_trace_free (stack_trace);
+
+  int_to_string ((uintptr_t) instance, instance_name, sizeof (instance_name));
+  instance_entry.instance = instance;
+  instance_entry.trace_hash_key = g_metrics_allocation_block_store_copy_with_name 
(metrics_allocation_block_store, trace_key, strlen (trace_key) + 1, 
"GMetricsStackTraceSamplerInstanceEntry::trace_hash_key");
+  g_metrics_table_set_record (sampler->instances_table, instance_name, &instance_entry);
+}
+
+void
+g_metrics_stack_trace_sampler_remove_sample (GMetricsStackTraceSampler *sampler,
+                                             gpointer                   instance)
+{
+  GMetricsStackTraceSample *sample;
+  GMetricsStackTraceSamplerInstanceEntry *instance_entry;
+  char *trace_key;
+  char instance_name[64] = "";
+
+  int_to_string ((uintptr_t) instance, instance_name, sizeof (instance_name));
+  instance_entry = g_metrics_table_get_record (sampler->instances_table, instance_name);
+
+  if (instance_entry == NULL)
+    return;
+
+  trace_key = instance_entry->trace_hash_key;
+  g_metrics_table_remove_record (sampler->instances_table, instance_name);
+
+  sample = g_metrics_table_get_record (sampler->traces_table, trace_key);
+
+  if (sample == NULL)
+    {
+      g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, trace_key);
+      return;
+    }
+
+  sample->number_of_hits--;
+
+  if (sample->number_of_hits == 0)
+    {
+      g_metrics_stack_trace_free (sample->stack_trace);
+      g_metrics_table_remove_record (sampler->traces_table, trace_key);
+    }
+
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, trace_key);
+}
+
+void
+g_metrics_stack_trace_sampler_clear (GMetricsStackTraceSampler *sampler)
+{
+  GMetricsTableIter iter;
+  const char *name;
+  GMetricsStackTraceSample *sample;
+  GMetricsStackTraceSamplerInstanceEntry *instance_entry;
+
+  g_metrics_table_iter_init (&iter, sampler->traces_table);
+  while (g_metrics_table_iter_next (&iter, &name, &sample))
+    g_metrics_stack_trace_free (sample->stack_trace);
+  g_metrics_table_clear (sampler->traces_table);
+
+  g_metrics_table_iter_init (&iter, sampler->instances_table);
+  while (g_metrics_table_iter_next (&iter, &name, &instance_entry))
+    g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store,
+                                                 instance_entry->trace_hash_key);
+  g_metrics_table_clear (sampler->instances_table);
+}
+
+void
+g_metrics_stack_trace_sampler_free (GMetricsStackTraceSampler *sampler)
+{
+  if (sampler == NULL)
+    return;
+
+  g_metrics_stack_trace_sampler_clear (sampler);
+  g_metrics_table_free (sampler->traces_table);
+  g_metrics_table_free (sampler->instances_table);
+  g_metrics_allocation_block_store_deallocate (metrics_allocation_block_store, sampler);
+}
+
+int
+g_metrics_get_timeout_fd (void)
+{
+  return timeout_fd;
+}
+
+const char *
+g_metrics_get_log_dir (void)
+{
+  return metrics_config.log_dir;
+}
diff --git a/glib/gmetrics.h b/glib/gmetrics.h
new file mode 100644
index 000000000..af061e67f
--- /dev/null
+++ b/glib/gmetrics.h
@@ -0,0 +1,333 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __G_METRICS_H__
+#define __G_METRICS_H__
+
+#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+#include <stdarg.h>
+#include <glib/gtypes.h>
+#include <glib/gmacros.h>
+#include <glib/gmain.h>
+
+G_BEGIN_DECLS
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_init (void);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_enabled (void);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_requested (const char *name);
+
+typedef void (*GMetricsTimeoutFunc) (void);
+
+GLIB_AVAILABLE_IN_ALL
+int g_metrics_get_timeout_fd (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_run_timeout_handlers (void);
+
+GLIB_AVAILABLE_IN_ALL
+gulong g_metrics_get_generation (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_start_timeout (GMetricsTimeoutFunc  timeout_handler);
+
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_get_log_dir (void);
+
+typedef struct _GMetricsFile GMetricsFile;
+GLIB_AVAILABLE_IN_ALL
+GMetricsFile *g_metrics_file_new (const char *name,
+                                  const char *first_column_name,
+                                  const char *first_column_format,
+                                  ...);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_start_record (GMetricsFile *metrics_file);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_add_row_without_cast (GMetricsFile *metrics_file,
+                                          gconstpointer first_column_value,
+                                          ...);
+#define g_metrics_file_add_row(_file,_first,_rest...) g_metrics_file_add_row_without_cast ((_file), 
(gpointer) (_first), _rest)
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_end_record (GMetricsFile *metrics_file);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_file_free (GMetricsFile *metrics_file);
+
+typedef struct _GMetricsTable GMetricsTable;
+GLIB_AVAILABLE_IN_ALL
+GMetricsTable *g_metrics_table_new (gsize record_size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_set_record (GMetricsTable *metrics_table,
+                                 const char    *name,
+                                 gpointer       record);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_table_get_record (GMetricsTable *metrics_table,
+                                     const char    *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_remove_record (GMetricsTable *metrics_table,
+                                    const char    *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_clear (GMetricsTable *metrics_table);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_free (GMetricsTable *metrics_table);
+
+typedef struct _GMetricsTableIter GMetricsTableIter;
+
+typedef struct _GMetricsTableEntry GMetricsTableEntry;
+struct _GMetricsTableIter
+{
+  /*< private >*/
+  GMetricsTableEntry *entry;
+};
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_iter_init (GMetricsTableIter *iter,
+                                GMetricsTable     *table);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_table_sorted_iter_init (GMetricsTableIter *iter,
+                                       GMetricsTable     *table,
+                                       GCompareFunc       comparison_function);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_table_iter_next_without_cast (GMetricsTableIter  *iter,
+                                                 const char        **name,
+                                                 gpointer           *record);
+#define g_metrics_table_iter_next(_iter,_name,_record) g_metrics_table_iter_next_without_cast ((_iter), 
(_name), (gpointer *) (_record))
+
+struct _GMetricsInstanceCounterMetrics
+{
+  char    comment[64];
+  gsize  total_memory_usage;
+  gsize  instance_count;
+  gssize instance_change;
+  gsize  instance_watermark;
+  gssize average_instance_change;
+  gsize  number_of_samples;
+};
+typedef struct _GMetricsInstanceCounterMetrics GMetricsInstanceCounterMetrics;
+
+typedef struct _GMetricsInstanceCounter GMetricsInstanceCounter;
+
+GLIB_AVAILABLE_IN_ALL
+GMetricsInstanceCounter *g_metrics_instance_counter_new (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_start_record (GMetricsInstanceCounter *counter);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_add_instance (GMetricsInstanceCounter *counter,
+                                              const char              *name,
+                                              gsize                   memory_usage);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_add_instances (GMetricsInstanceCounter *counter,
+                                               const char              *name,
+                                               const char              *comment,
+                                               gsize                   number_of_instances,
+                                               gsize                   total_usage);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_end_record (GMetricsInstanceCounter *counter);
+
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_instance_counter_instance_is_interesting (GMetricsInstanceCounter *counter,
+                                                             const char              *name);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_free (GMetricsInstanceCounter *counter);
+
+typedef struct _GMetricsInstanceCounterIter GMetricsInstanceCounterIter;
+
+typedef struct _GMetricsInstanceCounterEntry GMetricsInstanceCounterEntry;
+struct _GMetricsInstanceCounterIter
+{
+  /*< private >*/
+  gssize table_index;
+  GMetricsTableIter table_iter;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_instance_counter_iter_init (GMetricsInstanceCounterIter *iter,
+                                           GMetricsInstanceCounter     *table);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_instance_counter_iter_next (GMetricsInstanceCounterIter  *iter,
+                                               const char                **name,
+                                               GMetricsInstanceCounterMetrics **metrics);
+
+typedef struct _GMetricsList GMetricsList;
+GLIB_AVAILABLE_IN_ALL
+GMetricsList *g_metrics_list_new (void);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_add_item (GMetricsList *list,
+                              gpointer      item);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_remove_item (GMetricsList *list,
+                                 gpointer      item);
+GLIB_AVAILABLE_IN_ALL
+gsize g_metrics_list_get_length (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_list_get_last_item (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_remove_last_item (GMetricsList *list);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_free (GMetricsList *list);
+
+typedef struct _GMetricsListIter GMetricsListIter;
+typedef struct _GMetricsListNode GMetricsListNode;
+struct _GMetricsListIter
+{
+  /*< private >*/
+  GMetricsListNode *node;
+  GMetricsListNode *next_node;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_list_iter_init (GMetricsListIter *iter,
+                               GMetricsList     *list);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_list_iter_next_without_cast (GMetricsListIter *iter,
+                                                gpointer         *item);
+#define g_metrics_list_iter_next(_iter,_item) g_metrics_list_iter_next_without_cast ((_iter), (gpointer *) 
(_item))
+
+typedef struct _GMetricsStackTrace GMetricsStackTrace;
+
+GLIB_AVAILABLE_IN_ALL
+GMetricsStackTrace *g_metrics_stack_trace_new (int start_frame,
+                                               int number_of_frames,
+                                               const char *delimiter);
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_stack_trace_get_hash_key (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+const char *g_metrics_stack_trace_get_output (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_add_annotation (GMetricsStackTrace *stack_trace,
+                                           const char         *annotation);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_free (GMetricsStackTrace *stack_trace);
+
+GLIB_AVAILABLE_IN_ALL
+char *g_metrics_stack_trace (void);
+
+typedef struct _GMetricsStackTraceSampler GMetricsStackTraceSampler;
+typedef struct _GMetricsStackTraceSample GMetricsStackTraceSample;
+typedef struct _GMetricsStackTraceSamplerIter GMetricsStackTraceSamplerIter;
+
+struct _GMetricsStackTraceSamplerIter
+{
+  /*< private >*/
+  GMetricsTableIter table_iter;
+};
+
+struct _GMetricsStackTraceSample
+{
+  char name[64];
+  GMetricsStackTrace *stack_trace;
+  gsize number_of_hits;
+};
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_iter_init (GMetricsStackTraceSamplerIter *iter,
+                                              GMetricsStackTraceSampler     *sampler);
+GLIB_AVAILABLE_IN_ALL
+gboolean g_metrics_stack_trace_sampler_iter_next (GMetricsStackTraceSamplerIter   *iter,
+                                                  GMetricsStackTraceSample       **sample);
+
+typedef gboolean (* GMetricsStackTraceAnnotationHandler) (char *annotation, gsize annotation_size, gpointer 
user_data);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_set_stack_trace_annotation_handler (GMetricsStackTraceAnnotationHandler   handler,
+                                                   gpointer                          user_data);
+GLIB_AVAILABLE_IN_ALL
+GMetricsStackTraceSampler *g_metrics_stack_trace_sampler_new (void);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_take_sample (GMetricsStackTraceSampler *sample,
+                                                const char                *name,
+                                                gpointer                   instance);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_remove_sample (GMetricsStackTraceSampler *sampler,
+                                                  gpointer                   instance);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_clear (GMetricsStackTraceSampler *sampler);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_stack_trace_sampler_free (GMetricsStackTraceSampler *sampler);
+
+typedef struct _GMetricsAllocationBlockStore GMetricsAllocationBlockStore;
+GLIB_AVAILABLE_IN_ALL
+GMetricsAllocationBlockStore *g_metrics_allocation_block_store_new (const char *name,
+                                                                    gsize       size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_allocation_block_store_free (GMetricsAllocationBlockStore *block_store);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_allocate (GMetricsAllocationBlockStore *block_store,
+                                                    gsize                        size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_allocate_with_name (GMetricsAllocationBlockStore *block_store,
+                                                              gsize                         size,
+                                                              const char                   *name);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_reallocate (GMetricsAllocationBlockStore *block_store,
+                                                      gpointer                     allocation,
+                                                      gsize                        size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_copy (GMetricsAllocationBlockStore *block_store,
+                                                gconstpointer                allocation,
+                                                gsize                        size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocation_block_store_copy_with_name (GMetricsAllocationBlockStore *block_store,
+                                                          gconstpointer                 allocation,
+                                                          gsize                         size,
+                                                          const char                   *name);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_allocation_block_store_deallocate (GMetricsAllocationBlockStore *block_store,
+                                                  gpointer                     allocation);
+
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_push_default_allocation_block_store (GMetricsAllocationBlockStore *block_store);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_pop_default_allocation_block_store (void);
+
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_allocate (gsize size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_reallocate (gpointer allocation,
+                               gsize    size);
+GLIB_AVAILABLE_IN_ALL
+gpointer g_metrics_copy (gconstpointer allocation,
+                         gsize         size);
+GLIB_AVAILABLE_IN_ALL
+void g_metrics_free (gpointer allocation);
+G_END_DECLS
+
+#endif /* __G_METRICS_H__ */
diff --git a/glib/uthash.h b/glib/uthash.h
new file mode 100644
index 000000000..10780f2d5
--- /dev/null
+++ b/glib/uthash.h
@@ -0,0 +1,1138 @@
+/*
+Copyright (c) 2003-2021, Troy D. Hanson     http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.3.0
+
+#include <string.h>   /* memcmp, memset, strlen */
+#include <stddef.h>   /* ptrdiff_t */
+#include <stdlib.h>   /* exit */
+
+#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT
+/* This codepath is provided for backward compatibility, but I plan to remove it. */
+#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead"
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT
+#else
+#include <stdint.h>   /* uint8_t, uint32_t */
+#endif
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER)   /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else                   /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE(x)
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  char **_da_dst = (char**)(&(dst));                                             \
+  *_da_dst = (char*)(src);                                                       \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  (dst) = DECLTYPE(dst)(src);                                                    \
+} while (0)
+#endif
+
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */
+#endif
+#ifndef uthash_bzero
+#define uthash_bzero(a,n) memset(a,'\0',n)
+#endif
+#ifndef uthash_strlen
+#define uthash_strlen(s) strlen(s)
+#endif
+
+#ifndef HASH_FUNCTION
+#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)
+#endif
+
+#ifndef HASH_KEYCMP
+#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */
+#endif
+
+#ifndef HASH_NONFATAL_OOM
+#define HASH_NONFATAL_OOM 0
+#endif
+
+#if HASH_NONFATAL_OOM
+/* malloc failures can be recovered from */
+
+#ifndef uthash_nonfatal_oom
+#define uthash_nonfatal_oom(obj) do {} while (0)    /* non-fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)
+#define IF_HASH_NONFATAL_OOM(x) x
+
+#else
+/* malloc failures result in lost memory, hash tables are unusable */
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1)        /* fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory")
+#define IF_HASH_NONFATAL_OOM(x)
+
+#endif
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 512U     /* initial number of buckets        */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 9U /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 128U     /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhp */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* calculate the hash handle from element address elp */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))
+
+#define HASH_ROLLBACK_BKT(hh, head, itemptrhh)                                   \
+do {                                                                             \
+  struct UT_hash_handle *_hd_hh_item = (itemptrhh);                              \
+  unsigned _hd_bkt;                                                              \
+  HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);         \
+  (head)->hh.tbl->buckets[_hd_bkt].count++;                                      \
+  _hd_hh_item->hh_next = NULL;                                                   \
+  _hd_hh_item->hh_prev = NULL;                                                   \
+} while (0)
+
+#define HASH_VALUE(keyptr,keylen,hashv)                                          \
+do {                                                                             \
+  HASH_FUNCTION(keyptr, keylen, hashv);                                          \
+} while (0)
+
+#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \
+do {                                                                             \
+  (out) = NULL;                                                                  \
+  if (head) {                                                                    \
+    unsigned _hf_bkt;                                                            \
+    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \
+    if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \
+      HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, 
out); \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \
+do {                                                                             \
+  (out) = NULL;                                                                  \
+  if (head) {                                                                    \
+    unsigned _hf_hashv;                                                          \
+    HASH_VALUE(keyptr, keylen, _hf_hashv);                                       \
+    HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);             \
+  }                                                                              \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define HASH_BLOOM_MAKE(tbl,oomed)                                               \
+do {                                                                             \
+  (tbl)->bloom_nbits = HASH_BLOOM;                                               \
+  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \
+  if (!(tbl)->bloom_bv) {                                                        \
+    HASH_RECORD_OOM(oomed);                                                      \
+  } else {                                                                       \
+    uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                           \
+    (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                     \
+  }                                                                              \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl)                                                     \
+do {                                                                             \
+  uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                              \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define HASH_BLOOM_ADD(tbl,hashv)                                                \
+  HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#define HASH_BLOOM_TEST(tbl,hashv)                                               \
+  HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl,oomed)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0U
+#endif
+
+#define HASH_MAKE_TABLE(hh,head,oomed)                                           \
+do {                                                                             \
+  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table));         \
+  if (!(head)->hh.tbl) {                                                         \
+    HASH_RECORD_OOM(oomed);                                                      \
+  } else {                                                                       \
+    uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table));                         \
+    (head)->hh.tbl->tail = &((head)->hh);                                        \
+    (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                      \
+    (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;            \
+    (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                  \
+    (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                    \
+        HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));               \
+    (head)->hh.tbl->signature = HASH_SIGNATURE;                                  \
+    if (!(head)->hh.tbl->buckets) {                                              \
+      HASH_RECORD_OOM(oomed);                                                    \
+      uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                        \
+    } else {                                                                     \
+      uthash_bzero((head)->hh.tbl->buckets,                                      \
+          HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));             \
+      HASH_BLOOM_MAKE((head)->hh.tbl, oomed);                                    \
+      IF_HASH_NONFATAL_OOM(                                                      \
+        if (oomed) {                                                             \
+          uthash_free((head)->hh.tbl->buckets,                                   \
+              HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));           \
+          uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                    \
+        }                                                                        \
+      )                                                                          \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+    HASH_DELETE(hh, head, replaced);                                             \
+  }                                                                              \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+    HASH_DELETE(hh, head, replaced);                                             \
+  }                                                                              \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define HASH_APPEND_LIST(hh, head, add)                                          \
+do {                                                                             \
+  (add)->hh.next = NULL;                                                         \
+  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \
+  (head)->hh.tbl->tail->next = (add);                                            \
+  (head)->hh.tbl->tail = &((add)->hh);                                           \
+} while (0)
+
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \
+do {                                                                             \
+  do {                                                                           \
+    if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) {                             \
+      break;                                                                     \
+    }                                                                            \
+  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \
+} while (0)
+
+#ifdef NO_DECLTYPE
+#undef HASH_AKBI_INNER_LOOP
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \
+do {                                                                             \
+  char *_hs_saved_head = (char*)(head);                                          \
+  do {                                                                           \
+    DECLTYPE_ASSIGN(head, _hs_iter);                                             \
+    if (cmpfcn(head, add) > 0) {                                                 \
+      DECLTYPE_ASSIGN(head, _hs_saved_head);                                     \
+      break;                                                                     \
+    }                                                                            \
+    DECLTYPE_ASSIGN(head, _hs_saved_head);                                       \
+  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \
+} while (0)
+#endif
+
+#if HASH_NONFATAL_OOM
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \
+do {                                                                             \
+  if (!(oomed)) {                                                                \
+    unsigned _ha_bkt;                                                            \
+    (head)->hh.tbl->num_items++;                                                 \
+    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                  \
+    HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);    \
+    if (oomed) {                                                                 \
+      HASH_ROLLBACK_BKT(hh, head, &(add)->hh);                                   \
+      HASH_DELETE_HH(hh, head, &(add)->hh);                                      \
+      (add)->hh.tbl = NULL;                                                      \
+      uthash_nonfatal_oom(add);                                                  \
+    } else {                                                                     \
+      HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                   \
+      HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                \
+    }                                                                            \
+  } else {                                                                       \
+    (add)->hh.tbl = NULL;                                                        \
+    uthash_nonfatal_oom(add);                                                    \
+  }                                                                              \
+} while (0)
+
+#else
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed)            \
+do {                                                                             \
+  unsigned _ha_bkt;                                                              \
+  (head)->hh.tbl->num_items++;                                                   \
+  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \
+  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed);      \
+  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \
+  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \
+} while (0)
+
+#endif
+
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do {                                                                             \
+  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (char*) (keyptr);                                              \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \
+    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \
+      (head) = (add);                                                            \
+    IF_HASH_NONFATAL_OOM( } )                                                    \
+  } else {                                                                       \
+    void *_hs_iter = (head);                                                     \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn);                                 \
+    if (_hs_iter) {                                                              \
+      (add)->hh.next = _hs_iter;                                                 \
+      if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) {     \
+        HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add);              \
+      } else {                                                                   \
+        (head) = (add);                                                          \
+      }                                                                          \
+      HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add);                      \
+    } else {                                                                     \
+      HASH_APPEND_LIST(hh, head, add);                                           \
+    }                                                                            \
+  }                                                                              \
+  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \
+  HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER");                    \
+} while (0)
+
+#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \
+do {                                                                             \
+  unsigned _hs_hashv;                                                            \
+  HASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \
+  HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \
+do {                                                                             \
+  IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; )                                     \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (const void*) (keyptr);                                        \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    HASH_MAKE_TABLE(hh, add, _ha_oomed);                                         \
+    IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { )                                    \
+      (head) = (add);                                                            \
+    IF_HASH_NONFATAL_OOM( } )                                                    \
+  } else {                                                                       \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    HASH_APPEND_LIST(hh, head, add);                                             \
+  }                                                                              \
+  HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed);       \
+  HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE");                            \
+} while (0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \
+do {                                                                             \
+  unsigned _ha_hashv;                                                            \
+  HASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \
+  HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define HASH_TO_BKT(hashv,num_bkts,bkt)                                          \
+do {                                                                             \
+  bkt = ((hashv) & ((num_bkts) - 1U));                                           \
+} while (0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ *  HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr)                                              \
+    HASH_DELETE_HH(hh, head, &(delptr)->hh)
+
+#define HASH_DELETE_HH(hh,head,delptrhh)                                         \
+do {                                                                             \
+  struct UT_hash_handle *_hd_hh_del = (delptrhh);                                \
+  if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) {                \
+    HASH_BLOOM_FREE((head)->hh.tbl);                                             \
+    uthash_free((head)->hh.tbl->buckets,                                         \
+                (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket));    \
+    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \
+    (head) = NULL;                                                               \
+  } else {                                                                       \
+    unsigned _hd_bkt;                                                            \
+    if (_hd_hh_del == (head)->hh.tbl->tail) {                                    \
+      (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev);     \
+    }                                                                            \
+    if (_hd_hh_del->prev != NULL) {                                              \
+      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next;   \
+    } else {                                                                     \
+      DECLTYPE_ASSIGN(head, _hd_hh_del->next);                                   \
+    }                                                                            \
+    if (_hd_hh_del->next != NULL) {                                              \
+      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev;   \
+    }                                                                            \
+    HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);        \
+    HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);               \
+    (head)->hh.tbl->num_items--;                                                 \
+  }                                                                              \
+  HASH_FSCK(hh, head, "HASH_DELETE_HH");                                         \
+} while (0)
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out)                                          \
+do {                                                                             \
+    unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr);            \
+    HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out);                     \
+} while (0)
+#define HASH_ADD_STR(head,strfield,add)                                          \
+do {                                                                             \
+    unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield);    \
+    HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add);                  \
+} while (0)
+#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \
+do {                                                                             \
+    unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield);    \
+    HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced);    \
+} while (0)
+#define HASH_FIND_INT(head,findint,out)                                          \
+    HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add)                                          \
+    HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \
+    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out)                                          \
+    HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add)                                          \
+    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \
+    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr)                                                    \
+    HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#include <stdio.h>   /* fprintf, stderr */
+#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head,where)                                                 \
+do {                                                                             \
+  struct UT_hash_handle *_thh;                                                   \
+  if (head) {                                                                    \
+    unsigned _bkt_i;                                                             \
+    unsigned _count = 0;                                                         \
+    char *_prev;                                                                 \
+    for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) {           \
+      unsigned _bkt_count = 0;                                                   \
+      _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                            \
+      _prev = NULL;                                                              \
+      while (_thh) {                                                             \
+        if (_prev != (char*)(_thh->hh_prev)) {                                   \
+          HASH_OOPS("%s: invalid hh_prev %p, actual %p\n",                       \
+              (where), (void*)_thh->hh_prev, (void*)_prev);                      \
+        }                                                                        \
+        _bkt_count++;                                                            \
+        _prev = (char*)(_thh);                                                   \
+        _thh = _thh->hh_next;                                                    \
+      }                                                                          \
+      _count += _bkt_count;                                                      \
+      if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {                \
+        HASH_OOPS("%s: invalid bucket count %u, actual %u\n",                    \
+            (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);         \
+      }                                                                          \
+    }                                                                            \
+    if (_count != (head)->hh.tbl->num_items) {                                   \
+      HASH_OOPS("%s: invalid hh item count %u, actual %u\n",                     \
+          (where), (head)->hh.tbl->num_items, _count);                           \
+    }                                                                            \
+    _count = 0;                                                                  \
+    _prev = NULL;                                                                \
+    _thh =  &(head)->hh;                                                         \
+    while (_thh) {                                                               \
+      _count++;                                                                  \
+      if (_prev != (char*)_thh->prev) {                                          \
+        HASH_OOPS("%s: invalid prev %p, actual %p\n",                            \
+            (where), (void*)_thh->prev, (void*)_prev);                           \
+      }                                                                          \
+      _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                         \
+      _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL);     \
+    }                                                                            \
+    if (_count != (head)->hh.tbl->num_items) {                                   \
+      HASH_OOPS("%s: invalid app item count %u, actual %u\n",                    \
+          (where), (head)->hh.tbl->num_items, _count);                           \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+#else
+#define HASH_FSCK(hh,head,where)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \
+do {                                                                             \
+  unsigned _klen = fieldlen;                                                     \
+  write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                  \
+  write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                        \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
+#define HASH_BER(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hb_keylen = (unsigned)keylen;                                        \
+  const unsigned char *_hb_key = (const unsigned char*)(key);                    \
+  (hashv) = 0;                                                                   \
+  while (_hb_keylen-- != 0U) {                                                   \
+    (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                           \
+  }                                                                              \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define HASH_SAX(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _sx_i;                                                                \
+  const unsigned char *_hs_key = (const unsigned char*)(key);                    \
+  hashv = 0;                                                                     \
+  for (_sx_i=0; _sx_i < keylen; _sx_i++) {                                       \
+    hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                       \
+  }                                                                              \
+} while (0)
+/* FNV-1a variation */
+#define HASH_FNV(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _fn_i;                                                                \
+  const unsigned char *_hf_key = (const unsigned char*)(key);                    \
+  (hashv) = 2166136261U;                                                         \
+  for (_fn_i=0; _fn_i < keylen; _fn_i++) {                                       \
+    hashv = hashv ^ _hf_key[_fn_i];                                              \
+    hashv = hashv * 16777619U;                                                   \
+  }                                                                              \
+} while (0)
+
+#define HASH_OAT(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _ho_i;                                                                \
+  const unsigned char *_ho_key=(const unsigned char*)(key);                      \
+  hashv = 0;                                                                     \
+  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \
+      hashv += _ho_key[_ho_i];                                                   \
+      hashv += (hashv << 10);                                                    \
+      hashv ^= (hashv >> 6);                                                     \
+  }                                                                              \
+  hashv += (hashv << 3);                                                         \
+  hashv ^= (hashv >> 11);                                                        \
+  hashv += (hashv << 15);                                                        \
+} while (0)
+
+#define HASH_JEN_MIX(a,b,c)                                                      \
+do {                                                                             \
+  a -= b; a -= c; a ^= ( c >> 13 );                                              \
+  b -= c; b -= a; b ^= ( a << 8 );                                               \
+  c -= a; c -= b; c ^= ( b >> 13 );                                              \
+  a -= b; a -= c; a ^= ( c >> 12 );                                              \
+  b -= c; b -= a; b ^= ( a << 16 );                                              \
+  c -= a; c -= b; c ^= ( b >> 5 );                                               \
+  a -= b; a -= c; a ^= ( c >> 3 );                                               \
+  b -= c; b -= a; b ^= ( a << 10 );                                              \
+  c -= a; c -= b; c ^= ( b >> 15 );                                              \
+} while (0)
+
+#define HASH_JEN(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hj_i,_hj_j,_hj_k;                                                    \
+  unsigned const char *_hj_key=(unsigned const char*)(key);                      \
+  hashv = 0xfeedbeefu;                                                           \
+  _hj_i = _hj_j = 0x9e3779b9u;                                                   \
+  _hj_k = (unsigned)(keylen);                                                    \
+  while (_hj_k >= 12U) {                                                         \
+    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \
+        + ( (unsigned)_hj_key[2] << 16 )                                         \
+        + ( (unsigned)_hj_key[3] << 24 ) );                                      \
+    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \
+        + ( (unsigned)_hj_key[6] << 16 )                                         \
+        + ( (unsigned)_hj_key[7] << 24 ) );                                      \
+    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \
+        + ( (unsigned)_hj_key[10] << 16 )                                        \
+        + ( (unsigned)_hj_key[11] << 24 ) );                                     \
+                                                                                 \
+     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \
+                                                                                 \
+     _hj_key += 12;                                                              \
+     _hj_k -= 12U;                                                               \
+  }                                                                              \
+  hashv += (unsigned)(keylen);                                                   \
+  switch ( _hj_k ) {                                                             \
+    case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */         \
+    case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */         \
+    case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */         \
+    case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */         \
+    case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */         \
+    case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */         \
+    case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */         \
+    case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */         \
+    case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */         \
+    case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */         \
+    case 1:  _hj_i += _hj_key[0];                      /* FALLTHROUGH */         \
+    default: ;                                                                   \
+  }                                                                              \
+  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
+} while (0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \
+  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \
+                                                                                 \
+  unsigned _sfh_rem = _sfh_len & 3U;                                             \
+  _sfh_len >>= 2;                                                                \
+  hashv = 0xcafebabeu;                                                           \
+                                                                                 \
+  /* Main loop */                                                                \
+  for (;_sfh_len > 0U; _sfh_len--) {                                             \
+    hashv    += get16bits (_sfh_key);                                            \
+    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \
+    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \
+    _sfh_key += 2U*sizeof (uint16_t);                                            \
+    hashv    += hashv >> 11;                                                     \
+  }                                                                              \
+                                                                                 \
+  /* Handle end cases */                                                         \
+  switch (_sfh_rem) {                                                            \
+    case 3: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 16;                                                \
+            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \
+            hashv += hashv >> 11;                                                \
+            break;                                                               \
+    case 2: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 11;                                                \
+            hashv += hashv >> 17;                                                \
+            break;                                                               \
+    case 1: hashv += *_sfh_key;                                                  \
+            hashv ^= hashv << 10;                                                \
+            hashv += hashv >> 1;                                                 \
+            break;                                                               \
+    default: ;                                                                   \
+  }                                                                              \
+                                                                                 \
+  /* Force "avalanching" of final 127 bits */                                    \
+  hashv ^= hashv << 3;                                                           \
+  hashv += hashv >> 5;                                                           \
+  hashv ^= hashv << 4;                                                           \
+  hashv += hashv >> 17;                                                          \
+  hashv ^= hashv << 25;                                                          \
+  hashv += hashv >> 6;                                                           \
+} while (0)
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \
+do {                                                                             \
+  if ((head).hh_head != NULL) {                                                  \
+    DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head));                     \
+  } else {                                                                       \
+    (out) = NULL;                                                                \
+  }                                                                              \
+  while ((out) != NULL) {                                                        \
+    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \
+      if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) {                  \
+        break;                                                                   \
+      }                                                                          \
+    }                                                                            \
+    if ((out)->hh.hh_next != NULL) {                                             \
+      DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next));                \
+    } else {                                                                     \
+      (out) = NULL;                                                              \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+/* add an item to a bucket  */
+#define HASH_ADD_TO_BKT(head,hh,addhh,oomed)                                     \
+do {                                                                             \
+  UT_hash_bucket *_ha_head = &(head);                                            \
+  _ha_head->count++;                                                             \
+  (addhh)->hh_next = _ha_head->hh_head;                                          \
+  (addhh)->hh_prev = NULL;                                                       \
+  if (_ha_head->hh_head != NULL) {                                               \
+    _ha_head->hh_head->hh_prev = (addhh);                                        \
+  }                                                                              \
+  _ha_head->hh_head = (addhh);                                                   \
+  if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \
+      && !(addhh)->tbl->noexpand) {                                              \
+    HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed);                              \
+    IF_HASH_NONFATAL_OOM(                                                        \
+      if (oomed) {                                                               \
+        HASH_DEL_IN_BKT(head,addhh);                                             \
+      }                                                                          \
+    )                                                                            \
+  }                                                                              \
+} while (0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(head,delhh)                                              \
+do {                                                                             \
+  UT_hash_bucket *_hd_head = &(head);                                            \
+  _hd_head->count--;                                                             \
+  if (_hd_head->hh_head == (delhh)) {                                            \
+    _hd_head->hh_head = (delhh)->hh_next;                                        \
+  }                                                                              \
+  if ((delhh)->hh_prev) {                                                        \
+    (delhh)->hh_prev->hh_next = (delhh)->hh_next;                                \
+  }                                                                              \
+  if ((delhh)->hh_next) {                                                        \
+    (delhh)->hh_next->hh_prev = (delhh)->hh_prev;                                \
+  }                                                                              \
+} while (0)
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ *      ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(hh,tbl,oomed)                                        \
+do {                                                                             \
+  unsigned _he_bkt;                                                              \
+  unsigned _he_bkt_i;                                                            \
+  struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                   \
+  UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                  \
+  _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                              \
+           sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);             \
+  if (!_he_new_buckets) {                                                        \
+    HASH_RECORD_OOM(oomed);                                                      \
+  } else {                                                                       \
+    uthash_bzero(_he_new_buckets,                                                \
+        sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U);                \
+    (tbl)->ideal_chain_maxlen =                                                  \
+       ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) +                      \
+       ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);    \
+    (tbl)->nonideal_items = 0;                                                   \
+    for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) {           \
+      _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head;                             \
+      while (_he_thh != NULL) {                                                  \
+        _he_hh_nxt = _he_thh->hh_next;                                           \
+        HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt);           \
+        _he_newbkt = &(_he_new_buckets[_he_bkt]);                                \
+        if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) {                 \
+          (tbl)->nonideal_items++;                                               \
+          if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \
+            _he_newbkt->expand_mult++;                                           \
+          }                                                                      \
+        }                                                                        \
+        _he_thh->hh_prev = NULL;                                                 \
+        _he_thh->hh_next = _he_newbkt->hh_head;                                  \
+        if (_he_newbkt->hh_head != NULL) {                                       \
+          _he_newbkt->hh_head->hh_prev = _he_thh;                                \
+        }                                                                        \
+        _he_newbkt->hh_head = _he_thh;                                           \
+        _he_thh = _he_hh_nxt;                                                    \
+      }                                                                          \
+    }                                                                            \
+    uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
+    (tbl)->num_buckets *= 2U;                                                    \
+    (tbl)->log2_num_buckets++;                                                   \
+    (tbl)->buckets = _he_new_buckets;                                            \
+    (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ?   \
+        ((tbl)->ineff_expands+1U) : 0U;                                          \
+    if ((tbl)->ineff_expands > 1U) {                                             \
+      (tbl)->noexpand = 1;                                                       \
+      uthash_noexpand_fyi(tbl);                                                  \
+    }                                                                            \
+    uthash_expand_fyi(tbl);                                                      \
+  }                                                                              \
+} while (0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh.
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn) HASH_SRT_DATA(hh,head,cmpfcn,NULL)
+#define HASH_SRT_DATA(hh,head,cmpfcn,data)                                       \
+do {                                                                             \
+  unsigned _hs_i;                                                                \
+  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \
+  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \
+  if (head != NULL) {                                                            \
+    _hs_insize = 1;                                                              \
+    _hs_looping = 1;                                                             \
+    _hs_list = &((head)->hh);                                                    \
+    while (_hs_looping != 0U) {                                                  \
+      _hs_p = _hs_list;                                                          \
+      _hs_list = NULL;                                                           \
+      _hs_tail = NULL;                                                           \
+      _hs_nmerges = 0;                                                           \
+      while (_hs_p != NULL) {                                                    \
+        _hs_nmerges++;                                                           \
+        _hs_q = _hs_p;                                                           \
+        _hs_psize = 0;                                                           \
+        for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) {                           \
+          _hs_psize++;                                                           \
+          _hs_q = ((_hs_q->next != NULL) ?                                       \
+            HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                   \
+          if (_hs_q == NULL) {                                                   \
+            break;                                                               \
+          }                                                                      \
+        }                                                                        \
+        _hs_qsize = _hs_insize;                                                  \
+        while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) {    \
+          if (_hs_psize == 0U) {                                                 \
+            _hs_e = _hs_q;                                                       \
+            _hs_q = ((_hs_q->next != NULL) ?                                     \
+              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \
+            _hs_qsize--;                                                         \
+          } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) {                     \
+            _hs_e = _hs_p;                                                       \
+            if (_hs_p != NULL) {                                                 \
+              _hs_p = ((_hs_p->next != NULL) ?                                   \
+                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \
+            }                                                                    \
+            _hs_psize--;                                                         \
+          } else if ((cmpfcn(                                                    \
+                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)),             \
+                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)),             \
+                data                                                             \
+                )) <= 0) {                                                       \
+            _hs_e = _hs_p;                                                       \
+            if (_hs_p != NULL) {                                                 \
+              _hs_p = ((_hs_p->next != NULL) ?                                   \
+                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \
+            }                                                                    \
+            _hs_psize--;                                                         \
+          } else {                                                               \
+            _hs_e = _hs_q;                                                       \
+            _hs_q = ((_hs_q->next != NULL) ?                                     \
+              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \
+            _hs_qsize--;                                                         \
+          }                                                                      \
+          if ( _hs_tail != NULL ) {                                              \
+            _hs_tail->next = ((_hs_e != NULL) ?                                  \
+              ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL);                       \
+          } else {                                                               \
+            _hs_list = _hs_e;                                                    \
+          }                                                                      \
+          if (_hs_e != NULL) {                                                   \
+            _hs_e->prev = ((_hs_tail != NULL) ?                                  \
+              ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL);                    \
+          }                                                                      \
+          _hs_tail = _hs_e;                                                      \
+        }                                                                        \
+        _hs_p = _hs_q;                                                           \
+      }                                                                          \
+      if (_hs_tail != NULL) {                                                    \
+        _hs_tail->next = NULL;                                                   \
+      }                                                                          \
+      if (_hs_nmerges <= 1U) {                                                   \
+        _hs_looping = 0;                                                         \
+        (head)->hh.tbl->tail = _hs_tail;                                         \
+        DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list));           \
+      }                                                                          \
+      _hs_insize *= 2U;                                                          \
+    }                                                                            \
+    HASH_FSCK(hh, head, "HASH_SRT");                                             \
+  }                                                                              \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \
+do {                                                                             \
+  unsigned _src_bkt, _dst_bkt;                                                   \
+  void *_last_elt = NULL, *_elt;                                                 \
+  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \
+  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \
+  if ((src) != NULL) {                                                           \
+    for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {    \
+      for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;               \
+        _src_hh != NULL;                                                         \
+        _src_hh = _src_hh->hh_next) {                                            \
+        _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                         \
+        if (cond(_elt)) {                                                        \
+          IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; )                             \
+          _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho);          \
+          _dst_hh->key = _src_hh->key;                                           \
+          _dst_hh->keylen = _src_hh->keylen;                                     \
+          _dst_hh->hashv = _src_hh->hashv;                                       \
+          _dst_hh->prev = _last_elt;                                             \
+          _dst_hh->next = NULL;                                                  \
+          if (_last_elt_hh != NULL) {                                            \
+            _last_elt_hh->next = _elt;                                           \
+          }                                                                      \
+          if ((dst) == NULL) {                                                   \
+            DECLTYPE_ASSIGN(dst, _elt);                                          \
+            HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed);                             \
+            IF_HASH_NONFATAL_OOM(                                                \
+              if (_hs_oomed) {                                                   \
+                uthash_nonfatal_oom(_elt);                                       \
+                (dst) = NULL;                                                    \
+                continue;                                                        \
+              }                                                                  \
+            )                                                                    \
+          } else {                                                               \
+            _dst_hh->tbl = (dst)->hh_dst.tbl;                                    \
+          }                                                                      \
+          HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);      \
+          HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \
+          (dst)->hh_dst.tbl->num_items++;                                        \
+          IF_HASH_NONFATAL_OOM(                                                  \
+            if (_hs_oomed) {                                                     \
+              HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh);                           \
+              HASH_DELETE_HH(hh_dst, dst, _dst_hh);                              \
+              _dst_hh->tbl = NULL;                                               \
+              uthash_nonfatal_oom(_elt);                                         \
+              continue;                                                          \
+            }                                                                    \
+          )                                                                      \
+          HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv);                          \
+          _last_elt = _elt;                                                      \
+          _last_elt_hh = _dst_hh;                                                \
+        }                                                                        \
+      }                                                                          \
+    }                                                                            \
+  }                                                                              \
+  HASH_FSCK(hh_dst, dst, "HASH_SELECT");                                         \
+} while (0)
+
+#define HASH_CLEAR(hh,head)                                                      \
+do {                                                                             \
+  if ((head) != NULL) {                                                          \
+    HASH_BLOOM_FREE((head)->hh.tbl);                                             \
+    uthash_free((head)->hh.tbl->buckets,                                         \
+                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \
+    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \
+    (head) = NULL;                                                               \
+  }                                                                              \
+} while (0)
+
+#define HASH_OVERHEAD(hh,head)                                                   \
+ (((head) != NULL) ? (                                                           \
+ (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \
+          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \
+           sizeof(UT_hash_table)                                   +             \
+           (HASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define HASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \
+  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+   struct UT_hash_handle *hh_head;
+   unsigned count;
+
+   /* expand_mult is normally set to 0. In this situation, the max chain length
+    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+    * the bucket's chain exceeds this length, bucket expansion is triggered).
+    * However, setting expand_mult to a non-zero value delays bucket expansion
+    * (that would be triggered by additions to this particular bucket)
+    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+    * (The multiplier is simply expand_mult+1). The whole idea of this
+    * multiplier is to reduce bucket expansions, since they are expensive, in
+    * situations where we know that a particular bucket tends to be overused.
+    * It is better to let its chain length grow to a longer yet-still-bounded
+    * value, than to do an O(n) bucket expansion too often.
+    */
+   unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1u
+#define HASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+   UT_hash_bucket *buckets;
+   unsigned num_buckets, log2_num_buckets;
+   unsigned num_items;
+   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */
+   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+   /* in an ideal situation (all buckets used equally), no bucket would have
+    * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+   unsigned ideal_chain_maxlen;
+
+   /* nonideal_items is the number of items in the hash whose chain position
+    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+    * hash distribution; reaching them in a chain traversal takes >ideal steps */
+   unsigned nonideal_items;
+
+   /* ineffective expands occur when a bucket doubling was performed, but
+    * afterward, more than half the items in the hash had nonideal chain
+    * positions. If this happens on two consecutive expansions we inhibit any
+    * further expansion, as it's not helping; this happens when the hash
+    * function isn't a good fit for the key domain. When expansion is inhibited
+    * the hash will still work, albeit no longer in constant time. */
+   unsigned ineff_expands, noexpand;
+
+   uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+   uint8_t *bloom_bv;
+   uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+   struct UT_hash_table *tbl;
+   void *prev;                       /* prev element in app order      */
+   void *next;                       /* next element in app order      */
+   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */
+   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */
+   const void *key;                  /* ptr to enclosing struct's key  */
+   unsigned keylen;                  /* enclosing struct's key len     */
+   unsigned hashv;                   /* result of hash-fcn(key)        */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/glib/utlist.h b/glib/utlist.h
new file mode 100644
index 000000000..1979448a7
--- /dev/null
+++ b/glib/utlist.h
@@ -0,0 +1,1073 @@
+/*
+Copyright (c) 2007-2021, Troy D. Hanson   http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 2.3.0
+
+#include <assert.h>
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros:  singly-linked lists.
+ * 2. DL_ macros:  doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ *      int id;
+ *      struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ *      struct item *item;
+ *      ... allocate and populate item ...
+ *      DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(LDECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER)   /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else                   /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define IF_NO_DECLTYPE(x) x
+#define LDECLTYPE(x) char*
+#define UTLIST_SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define UTLIST_NEXT(elt,list,next) ((char*)((list)->next))
+#define UTLIST_NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define UTLIST_PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define UTLIST_PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define UTLIST_RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define UTLIST_CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define IF_NO_DECLTYPE(x)
+#define UTLIST_SV(elt,list)
+#define UTLIST_NEXT(elt,list,next) ((elt)->next)
+#define UTLIST_NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define UTLIST_PREV(elt,list,prev) ((elt)->prev) */
+#define UTLIST_PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define UTLIST_RS(list)
+#define UTLIST_CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort    *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables.  *
+ *****************************************************************************/
+#define LL_SORT(list, cmp)                                                                     \
+    LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;)                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list);        \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) {                                    \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          } else if (_ls_qsize == 0 || !_ls_q) {                                               \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      if (_ls_tail) {                                                                          \
+        UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list);   \
+      }                                                                                        \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+
+#define DL_SORT(list, cmp)                                                                     \
+    DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next)                                                        \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;)                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list);        \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while ((_ls_psize > 0) || ((_ls_qsize > 0) && _ls_q)) {                                \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          } else if ((_ls_qsize == 0) || (!_ls_q)) {                                           \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list);   \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      UTLIST_CASTASGN((list)->prev, _ls_tail);                                                 \
+      UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list);     \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define CDL_SORT(list, cmp)                                                                    \
+    CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next)                                                       \
+do {                                                                                           \
+  LDECLTYPE(list) _ls_p;                                                                       \
+  LDECLTYPE(list) _ls_q;                                                                       \
+  LDECLTYPE(list) _ls_e;                                                                       \
+  LDECLTYPE(list) _ls_tail;                                                                    \
+  LDECLTYPE(list) _ls_oldhead;                                                                 \
+  LDECLTYPE(list) _tmp;                                                                        \
+  int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping;                       \
+  if (list) {                                                                                  \
+    _ls_insize = 1;                                                                            \
+    _ls_looping = 1;                                                                           \
+    while (_ls_looping) {                                                                      \
+      UTLIST_CASTASGN(_ls_p,list);                                                             \
+      UTLIST_CASTASGN(_ls_oldhead,list);                                                       \
+      (list) = NULL;                                                                           \
+      _ls_tail = NULL;                                                                         \
+      _ls_nmerges = 0;                                                                         \
+      while (_ls_p) {                                                                          \
+        _ls_nmerges++;                                                                         \
+        _ls_q = _ls_p;                                                                         \
+        _ls_psize = 0;                                                                         \
+        for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) {                                         \
+          _ls_psize++;                                                                         \
+          UTLIST_SV(_ls_q,list);                                                               \
+          if (UTLIST_NEXT(_ls_q,list,next) == _ls_oldhead) {                                   \
+            _ls_q = NULL;                                                                      \
+          } else {                                                                             \
+            _ls_q = UTLIST_NEXT(_ls_q,list,next);                                              \
+          }                                                                                    \
+          UTLIST_RS(list);                                                                     \
+          if (!_ls_q) break;                                                                   \
+        }                                                                                      \
+        _ls_qsize = _ls_insize;                                                                \
+        while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) {                                    \
+          if (_ls_psize == 0) {                                                                \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+            if (_ls_q == _ls_oldhead) { _ls_q = NULL; }                                        \
+          } else if (_ls_qsize == 0 || !_ls_q) {                                               \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+            if (_ls_p == _ls_oldhead) { _ls_p = NULL; }                                        \
+          } else if (cmp(_ls_p,_ls_q) <= 0) {                                                  \
+            _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p =                                      \
+              UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--;                      \
+            if (_ls_p == _ls_oldhead) { _ls_p = NULL; }                                        \
+          } else {                                                                             \
+            _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q =                                      \
+              UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--;                      \
+            if (_ls_q == _ls_oldhead) { _ls_q = NULL; }                                        \
+          }                                                                                    \
+          if (_ls_tail) {                                                                      \
+            UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+          } else {                                                                             \
+            UTLIST_CASTASGN(list,_ls_e);                                                       \
+          }                                                                                    \
+          UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list);   \
+          _ls_tail = _ls_e;                                                                    \
+        }                                                                                      \
+        _ls_p = _ls_q;                                                                         \
+      }                                                                                        \
+      UTLIST_CASTASGN((list)->prev,_ls_tail);                                                  \
+      UTLIST_CASTASGN(_tmp,list);                                                              \
+      UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_tmp,next); UTLIST_RS(list);     \
+      if (_ls_nmerges <= 1) {                                                                  \
+        _ls_looping=0;                                                                         \
+      }                                                                                        \
+      _ls_insize *= 2;                                                                         \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular)                                   *
+ *****************************************************************************/
+#define LL_PREPEND(head,add)                                                                   \
+    LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next)                                                             \
+do {                                                                                           \
+  (add)->next = (head);                                                                        \
+  (head) = (add);                                                                              \
+} while (0)
+
+#define LL_CONCAT(head1,head2)                                                                 \
+    LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next)                                                           \
+do {                                                                                           \
+  LDECLTYPE(head1) _tmp;                                                                       \
+  if (head1) {                                                                                 \
+    _tmp = (head1);                                                                            \
+    while (_tmp->next) { _tmp = _tmp->next; }                                                  \
+    _tmp->next=(head2);                                                                        \
+  } else {                                                                                     \
+    (head1)=(head2);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#define LL_APPEND(head,add)                                                                    \
+    LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  (add)->next=NULL;                                                                            \
+  if (head) {                                                                                  \
+    _tmp = (head);                                                                             \
+    while (_tmp->next) { _tmp = _tmp->next; }                                                  \
+    _tmp->next=(add);                                                                          \
+  } else {                                                                                     \
+    (head)=(add);                                                                              \
+  }                                                                                            \
+} while (0)
+
+#define LL_INSERT_INORDER(head,add,cmp)                                                        \
+    LL_INSERT_INORDER2(head,add,cmp,next)
+
+#define LL_INSERT_INORDER2(head,add,cmp,next)                                                  \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    LL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                               \
+    LL_APPEND_ELEM2(head, _tmp, add, next);                                                    \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->next = NULL;                                                                       \
+  }                                                                                            \
+} while (0)
+
+#define LL_LOWER_BOUND(head,elt,like,cmp)                                                      \
+    LL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define LL_LOWER_BOUND2(head,elt,like,cmp,next)                                                \
+  do {                                                                                         \
+    if ((head) == NULL || (cmp(head, like)) >= 0) {                                            \
+      (elt) = NULL;                                                                            \
+    } else {                                                                                   \
+      for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) {                         \
+        if (cmp((elt)->next, like) >= 0) {                                                     \
+          break;                                                                               \
+        }                                                                                      \
+      }                                                                                        \
+    }                                                                                          \
+  } while (0)
+
+#define LL_DELETE(head,del)                                                                    \
+    LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next)                                                              \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if ((head) == (del)) {                                                                       \
+    (head)=(head)->next;                                                                       \
+  } else {                                                                                     \
+    _tmp = (head);                                                                             \
+    while (_tmp->next && (_tmp->next != (del))) {                                              \
+      _tmp = _tmp->next;                                                                       \
+    }                                                                                          \
+    if (_tmp->next) {                                                                          \
+      _tmp->next = (del)->next;                                                                \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define LL_COUNT(head,el,counter)                                                              \
+    LL_COUNT2(head,el,counter,next)                                                            \
+
+#define LL_COUNT2(head,el,counter,next)                                                        \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  LL_FOREACH2(head,el,next) { ++(counter); }                                                   \
+} while (0)
+
+#define LL_FOREACH(head,el)                                                                    \
+    LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next)                                                              \
+    for ((el) = (head); el; (el) = (el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp)                                                           \
+    LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next)                                                     \
+  for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+#define LL_SEARCH_SCALAR(head,out,field,val)                                                   \
+    LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next)                                             \
+do {                                                                                           \
+    LL_FOREACH2(head,out,next) {                                                               \
+      if ((out)->field == (val)) break;                                                        \
+    }                                                                                          \
+} while (0)
+
+#define LL_SEARCH(head,out,elt,cmp)                                                            \
+    LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next)                                                      \
+do {                                                                                           \
+    LL_FOREACH2(head,out,next) {                                                               \
+      if ((cmp(out,elt))==0) break;                                                            \
+    }                                                                                          \
+} while (0)
+
+#define LL_REPLACE_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+ LDECLTYPE(head) _tmp;                                                                         \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ (add)->next = (el)->next;                                                                     \
+ if ((head) == (el)) {                                                                         \
+  (head) = (add);                                                                              \
+ } else {                                                                                      \
+  _tmp = (head);                                                                               \
+  while (_tmp->next && (_tmp->next != (el))) {                                                 \
+   _tmp = _tmp->next;                                                                          \
+  }                                                                                            \
+  if (_tmp->next) {                                                                            \
+    _tmp->next = (add);                                                                        \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define LL_REPLACE_ELEM(head, el, add)                                                         \
+    LL_REPLACE_ELEM2(head, el, add, next)
+
+#define LL_PREPEND_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  LDECLTYPE(head) _tmp;                                                                        \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el);                                                                          \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  } else {                                                                                     \
+   _tmp = (head);                                                                              \
+   while (_tmp->next && (_tmp->next != (el))) {                                                \
+    _tmp = _tmp->next;                                                                         \
+   }                                                                                           \
+   if (_tmp->next) {                                                                           \
+     _tmp->next = (add);                                                                       \
+   }                                                                                           \
+  }                                                                                            \
+ } else {                                                                                      \
+  LL_APPEND2(head, add, next);                                                                 \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define LL_PREPEND_ELEM(head, el, add)                                                         \
+    LL_PREPEND_ELEM2(head, el, add, next)
+
+#define LL_APPEND_ELEM2(head, el, add, next)                                                   \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (el)->next = (add);                                                                          \
+ } else {                                                                                      \
+  LL_PREPEND2(head, add, next);                                                                \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define LL_APPEND_ELEM(head, el, add)                                                          \
+    LL_APPEND_ELEM2(head, el, add, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef LL_CONCAT2
+#define LL_CONCAT2(head1,head2,next)                                                           \
+do {                                                                                           \
+  char *_tmp;                                                                                  \
+  if (head1) {                                                                                 \
+    _tmp = (char*)(head1);                                                                     \
+    while ((head1)->next) { (head1) = (head1)->next; }                                         \
+    (head1)->next = (head2);                                                                   \
+    UTLIST_RS(head1);                                                                          \
+  } else {                                                                                     \
+    (head1)=(head2);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_APPEND2
+#define LL_APPEND2(head,add,next)                                                              \
+do {                                                                                           \
+  if (head) {                                                                                  \
+    (add)->next = head;     /* use add->next as a temp variable */                             \
+    while ((add)->next->next) { (add)->next = (add)->next->next; }                             \
+    (add)->next->next=(add);                                                                   \
+  } else {                                                                                     \
+    (head)=(add);                                                                              \
+  }                                                                                            \
+  (add)->next=NULL;                                                                            \
+} while (0)
+
+#undef LL_INSERT_INORDER2
+#define LL_INSERT_INORDER2(head,add,cmp,next)                                                  \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, add)) >= 0) {                                               \
+    (add)->next = (head);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next != NULL && (cmp((head)->next, add)) < 0) {                             \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->next = (head)->next;                                                                \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_DELETE2
+#define LL_DELETE2(head,del,next)                                                              \
+do {                                                                                           \
+  if ((head) == (del)) {                                                                       \
+    (head)=(head)->next;                                                                       \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next && ((head)->next != (del))) {                                          \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    if ((head)->next) {                                                                        \
+      (head)->next = ((del)->next);                                                            \
+    }                                                                                          \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+
+#undef LL_REPLACE_ELEM2
+#define LL_REPLACE_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+  assert((head) != NULL);                                                                      \
+  assert((el) != NULL);                                                                        \
+  assert((add) != NULL);                                                                       \
+  if ((head) == (el)) {                                                                        \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    (add)->next = head;                                                                        \
+    while ((add)->next->next && ((add)->next->next != (el))) {                                 \
+      (add)->next = (add)->next->next;                                                         \
+    }                                                                                          \
+    if ((add)->next->next) {                                                                   \
+      (add)->next->next = (add);                                                               \
+    }                                                                                          \
+  }                                                                                            \
+  (add)->next = (el)->next;                                                                    \
+} while (0)
+
+#undef LL_PREPEND_ELEM2
+#define LL_PREPEND_ELEM2(head, el, add, next)                                                  \
+do {                                                                                           \
+  if (el) {                                                                                    \
+    assert((head) != NULL);                                                                    \
+    assert((add) != NULL);                                                                     \
+    if ((head) == (el)) {                                                                      \
+      (head) = (add);                                                                          \
+    } else {                                                                                   \
+      (add)->next = (head);                                                                    \
+      while ((add)->next->next && ((add)->next->next != (el))) {                               \
+        (add)->next = (add)->next->next;                                                       \
+      }                                                                                        \
+      if ((add)->next->next) {                                                                 \
+        (add)->next->next = (add);                                                             \
+      }                                                                                        \
+    }                                                                                          \
+    (add)->next = (el);                                                                        \
+  } else {                                                                                     \
+    LL_APPEND2(head, add, next);                                                               \
+  }                                                                                            \
+} while (0)                                                                                    \
+
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * doubly linked list macros (non-circular)                                   *
+ *****************************************************************************/
+#define DL_PREPEND(head,add)                                                                   \
+    DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next)                                                        \
+do {                                                                                           \
+ (add)->next = (head);                                                                         \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (head)->prev = (add);                                                                       \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+ }                                                                                             \
+ (head) = (add);                                                                               \
+} while (0)
+
+#define DL_APPEND(head,add)                                                                    \
+    DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next)                                                         \
+do {                                                                                           \
+  if (head) {                                                                                  \
+      (add)->prev = (head)->prev;                                                              \
+      (head)->prev->next = (add);                                                              \
+      (head)->prev = (add);                                                                    \
+      (add)->next = NULL;                                                                      \
+  } else {                                                                                     \
+      (head)=(add);                                                                            \
+      (head)->prev = (head);                                                                   \
+      (head)->next = NULL;                                                                     \
+  }                                                                                            \
+} while (0)
+
+#define DL_INSERT_INORDER(head,add,cmp)                                                        \
+    DL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next)                                             \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    DL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                               \
+    DL_APPEND_ELEM2(head, _tmp, add, prev, next);                                              \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->prev = (head);                                                                     \
+    (head)->next = NULL;                                                                       \
+  }                                                                                            \
+} while (0)
+
+#define DL_LOWER_BOUND(head,elt,like,cmp)                                                      \
+    DL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define DL_LOWER_BOUND2(head,elt,like,cmp,next)                                                \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, like)) >= 0) {                                              \
+    (elt) = NULL;                                                                              \
+  } else {                                                                                     \
+    for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) {                           \
+      if ((cmp((elt)->next, like)) >= 0) {                                                     \
+        break;                                                                                 \
+      }                                                                                        \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define DL_CONCAT(head1,head2)                                                                 \
+    DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next)                                                      \
+do {                                                                                           \
+  LDECLTYPE(head1) _tmp;                                                                       \
+  if (head2) {                                                                                 \
+    if (head1) {                                                                               \
+        UTLIST_CASTASGN(_tmp, (head2)->prev);                                                  \
+        (head2)->prev = (head1)->prev;                                                         \
+        (head1)->prev->next = (head2);                                                         \
+        UTLIST_CASTASGN((head1)->prev, _tmp);                                                  \
+    } else {                                                                                   \
+        (head1)=(head2);                                                                       \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define DL_DELETE(head,del)                                                                    \
+    DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next)                                                         \
+do {                                                                                           \
+  assert((head) != NULL);                                                                      \
+  assert((del)->prev != NULL);                                                                 \
+  if ((del)->prev == (del)) {                                                                  \
+      (head)=NULL;                                                                             \
+  } else if ((del)==(head)) {                                                                  \
+      (del)->next->prev = (del)->prev;                                                         \
+      (head) = (del)->next;                                                                    \
+  } else {                                                                                     \
+      (del)->prev->next = (del)->next;                                                         \
+      if ((del)->next) {                                                                       \
+          (del)->next->prev = (del)->prev;                                                     \
+      } else {                                                                                 \
+          (head)->prev = (del)->prev;                                                          \
+      }                                                                                        \
+  }                                                                                            \
+} while (0)
+
+#define DL_COUNT(head,el,counter)                                                              \
+    DL_COUNT2(head,el,counter,next)                                                            \
+
+#define DL_COUNT2(head,el,counter,next)                                                        \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  DL_FOREACH2(head,el,next) { ++(counter); }                                                   \
+} while (0)
+
+#define DL_FOREACH(head,el)                                                                    \
+    DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next)                                                              \
+    for ((el) = (head); el; (el) = (el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp)                                                           \
+    DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next)                                                     \
+  for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ if ((head) == (el)) {                                                                         \
+  (head) = (add);                                                                              \
+  (add)->next = (el)->next;                                                                    \
+  if ((el)->next == NULL) {                                                                    \
+   (add)->prev = (add);                                                                        \
+  } else {                                                                                     \
+   (add)->prev = (el)->prev;                                                                   \
+   (add)->next->prev = (add);                                                                  \
+  }                                                                                            \
+ } else {                                                                                      \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el)->prev;                                                                    \
+  (add)->prev->next = (add);                                                                   \
+  if ((el)->next == NULL) {                                                                    \
+   (head)->prev = (add);                                                                       \
+  } else {                                                                                     \
+   (add)->next->prev = (add);                                                                  \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define DL_REPLACE_ELEM(head, el, add)                                                         \
+    DL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define DL_PREPEND_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el);                                                                          \
+  (add)->prev = (el)->prev;                                                                    \
+  (el)->prev = (add);                                                                          \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  } else {                                                                                     \
+   (add)->prev->next = (add);                                                                  \
+  }                                                                                            \
+ } else {                                                                                      \
+  DL_APPEND2(head, add, prev, next);                                                           \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define DL_PREPEND_ELEM(head, el, add)                                                         \
+    DL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define DL_APPEND_ELEM2(head, el, add, prev, next)                                             \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el);                                                                          \
+  (el)->next = (add);                                                                          \
+  if ((add)->next) {                                                                           \
+   (add)->next->prev = (add);                                                                  \
+  } else {                                                                                     \
+   (head)->prev = (add);                                                                       \
+  }                                                                                            \
+ } else {                                                                                      \
+  DL_PREPEND2(head, add, prev, next);                                                          \
+ }                                                                                             \
+} while (0)                                                                                    \
+
+#define DL_APPEND_ELEM(head, el, add)                                                          \
+   DL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef DL_INSERT_INORDER2
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next)                                             \
+do {                                                                                           \
+  if ((head) == NULL) {                                                                        \
+    (add)->prev = (add);                                                                       \
+    (add)->next = NULL;                                                                        \
+    (head) = (add);                                                                            \
+  } else if ((cmp(head, add)) >= 0) {                                                          \
+    (add)->prev = (head)->prev;                                                                \
+    (add)->next = (head);                                                                      \
+    (head)->prev = (add);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((head)->next && (cmp((head)->next, add)) < 0) {                                     \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->prev = (head);                                                                      \
+    (add)->next = (head)->next;                                                                \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+    if ((add)->next) {                                                                         \
+      (add)->next->prev = (add);                                                               \
+    } else {                                                                                   \
+      (head)->prev = (add);                                                                    \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * circular doubly linked list macros                                         *
+ *****************************************************************************/
+#define CDL_APPEND(head,add)                                                                   \
+    CDL_APPEND2(head,add,prev,next)
+
+#define CDL_APPEND2(head,add,prev,next)                                                        \
+do {                                                                                           \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (add)->next = (head);                                                                       \
+   (head)->prev = (add);                                                                       \
+   (add)->prev->next = (add);                                                                  \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+   (add)->next = (add);                                                                        \
+   (head) = (add);                                                                             \
+ }                                                                                             \
+} while (0)
+
+#define CDL_PREPEND(head,add)                                                                  \
+    CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next)                                                       \
+do {                                                                                           \
+ if (head) {                                                                                   \
+   (add)->prev = (head)->prev;                                                                 \
+   (add)->next = (head);                                                                       \
+   (head)->prev = (add);                                                                       \
+   (add)->prev->next = (add);                                                                  \
+ } else {                                                                                      \
+   (add)->prev = (add);                                                                        \
+   (add)->next = (add);                                                                        \
+ }                                                                                             \
+ (head) = (add);                                                                               \
+} while (0)
+
+#define CDL_INSERT_INORDER(head,add,cmp)                                                       \
+    CDL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next)                                            \
+do {                                                                                           \
+  LDECLTYPE(head) _tmp;                                                                        \
+  if (head) {                                                                                  \
+    CDL_LOWER_BOUND2(head, _tmp, add, cmp, next);                                              \
+    CDL_APPEND_ELEM2(head, _tmp, add, prev, next);                                             \
+  } else {                                                                                     \
+    (head) = (add);                                                                            \
+    (head)->next = (head);                                                                     \
+    (head)->prev = (head);                                                                     \
+  }                                                                                            \
+} while (0)
+
+#define CDL_LOWER_BOUND(head,elt,like,cmp)                                                     \
+    CDL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define CDL_LOWER_BOUND2(head,elt,like,cmp,next)                                               \
+do {                                                                                           \
+  if ((head) == NULL || (cmp(head, like)) >= 0) {                                              \
+    (elt) = NULL;                                                                              \
+  } else {                                                                                     \
+    for ((elt) = (head); (elt)->next != (head); (elt) = (elt)->next) {                         \
+      if ((cmp((elt)->next, like)) >= 0) {                                                     \
+        break;                                                                                 \
+      }                                                                                        \
+    }                                                                                          \
+  }                                                                                            \
+} while (0)
+
+#define CDL_DELETE(head,del)                                                                   \
+    CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next)                                                        \
+do {                                                                                           \
+  if (((head)==(del)) && ((head)->next == (head))) {                                           \
+      (head) = NULL;                                                                           \
+  } else {                                                                                     \
+     (del)->next->prev = (del)->prev;                                                          \
+     (del)->prev->next = (del)->next;                                                          \
+     if ((del) == (head)) (head)=(del)->next;                                                  \
+  }                                                                                            \
+} while (0)
+
+#define CDL_COUNT(head,el,counter)                                                             \
+    CDL_COUNT2(head,el,counter,next)                                                           \
+
+#define CDL_COUNT2(head, el, counter,next)                                                     \
+do {                                                                                           \
+  (counter) = 0;                                                                               \
+  CDL_FOREACH2(head,el,next) { ++(counter); }                                                  \
+} while (0)
+
+#define CDL_FOREACH(head,el)                                                                   \
+    CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next)                                                             \
+    for ((el)=(head);el;(el)=(((el)->next==(head)) ? NULL : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2)                                                    \
+    CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)                                         \
+  for ((el) = (head), (tmp1) = (head) ? (head)->prev : NULL;                                   \
+       (el) && ((tmp2) = (el)->next, 1);                                                       \
+       (el) = ((el) == (tmp1) ? NULL : (tmp2)))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val)                                                  \
+    CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next)                                            \
+do {                                                                                           \
+    CDL_FOREACH2(head,out,next) {                                                              \
+      if ((out)->field == (val)) break;                                                        \
+    }                                                                                          \
+} while (0)
+
+#define CDL_SEARCH(head,out,elt,cmp)                                                           \
+    CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next)                                                     \
+do {                                                                                           \
+    CDL_FOREACH2(head,out,next) {                                                              \
+      if ((cmp(out,elt))==0) break;                                                            \
+    }                                                                                          \
+} while (0)
+
+#define CDL_REPLACE_ELEM2(head, el, add, prev, next)                                           \
+do {                                                                                           \
+ assert((head) != NULL);                                                                       \
+ assert((el) != NULL);                                                                         \
+ assert((add) != NULL);                                                                        \
+ if ((el)->next == (el)) {                                                                     \
+  (add)->next = (add);                                                                         \
+  (add)->prev = (add);                                                                         \
+  (head) = (add);                                                                              \
+ } else {                                                                                      \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el)->prev;                                                                    \
+  (add)->next->prev = (add);                                                                   \
+  (add)->prev->next = (add);                                                                   \
+  if ((head) == (el)) {                                                                        \
+   (head) = (add);                                                                             \
+  }                                                                                            \
+ }                                                                                             \
+} while (0)
+
+#define CDL_REPLACE_ELEM(head, el, add)                                                        \
+    CDL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define CDL_PREPEND_ELEM2(head, el, add, prev, next)                                           \
+do {                                                                                           \
+  if (el) {                                                                                    \
+    assert((head) != NULL);                                                                    \
+    assert((add) != NULL);                                                                     \
+    (add)->next = (el);                                                                        \
+    (add)->prev = (el)->prev;                                                                  \
+    (el)->prev = (add);                                                                        \
+    (add)->prev->next = (add);                                                                 \
+    if ((head) == (el)) {                                                                      \
+      (head) = (add);                                                                          \
+    }                                                                                          \
+  } else {                                                                                     \
+    CDL_APPEND2(head, add, prev, next);                                                        \
+  }                                                                                            \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add)                                                        \
+    CDL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define CDL_APPEND_ELEM2(head, el, add, prev, next)                                            \
+do {                                                                                           \
+ if (el) {                                                                                     \
+  assert((head) != NULL);                                                                      \
+  assert((add) != NULL);                                                                       \
+  (add)->next = (el)->next;                                                                    \
+  (add)->prev = (el);                                                                          \
+  (el)->next = (add);                                                                          \
+  (add)->next->prev = (add);                                                                   \
+ } else {                                                                                      \
+  CDL_PREPEND2(head, add, prev, next);                                                         \
+ }                                                                                             \
+} while (0)
+
+#define CDL_APPEND_ELEM(head, el, add)                                                         \
+    CDL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef CDL_INSERT_INORDER2
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next)                                            \
+do {                                                                                           \
+  if ((head) == NULL) {                                                                        \
+    (add)->prev = (add);                                                                       \
+    (add)->next = (add);                                                                       \
+    (head) = (add);                                                                            \
+  } else if ((cmp(head, add)) >= 0) {                                                          \
+    (add)->prev = (head)->prev;                                                                \
+    (add)->next = (head);                                                                      \
+    (add)->prev->next = (add);                                                                 \
+    (head)->prev = (add);                                                                      \
+    (head) = (add);                                                                            \
+  } else {                                                                                     \
+    char *_tmp = (char*)(head);                                                                \
+    while ((char*)(head)->next != _tmp && (cmp((head)->next, add)) < 0) {                      \
+      (head) = (head)->next;                                                                   \
+    }                                                                                          \
+    (add)->prev = (head);                                                                      \
+    (add)->next = (head)->next;                                                                \
+    (add)->next->prev = (add);                                                                 \
+    (head)->next = (add);                                                                      \
+    UTLIST_RS(head);                                                                           \
+  }                                                                                            \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+#endif /* UTLIST_H */
diff --git a/glib/utstring.h b/glib/utstring.h
new file mode 100644
index 000000000..be8904949
--- /dev/null
+++ b/glib/utstring.h
@@ -0,0 +1,411 @@
+/*
+Copyright (c) 2008-2021, Troy D. Hanson   http://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a dynamic string implementation using macros
+ */
+#ifndef UTSTRING_H
+#define UTSTRING_H
+
+#define UTSTRING_VERSION 2.3.0
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __GNUC__
+#define UTSTRING_UNUSED __attribute__((__unused__))
+#else
+#define UTSTRING_UNUSED
+#endif
+
+#ifdef oom
+#error "The name of macro 'oom' has been changed to 'utstring_oom'. Please update your code."
+#define utstring_oom() oom()
+#endif
+
+#ifndef utstring_oom
+#define utstring_oom() exit(-1)
+#endif
+
+extern void *__libc_malloc (size_t size);
+extern void *__libc_realloc (void *ptr, size_t size);
+extern void __libc_free (void *ptr);
+
+typedef struct {
+    char *d;  /* pointer to allocated buffer */
+    size_t n; /* allocated capacity */
+    size_t i; /* index of first unused byte */
+} UT_string;
+
+#define utstring_reserve(s,amt)                            \
+do {                                                       \
+  if (((s)->n - (s)->i) < (size_t)(amt)) {                 \
+    char *utstring_tmp = (char*)__libc_realloc(            \
+      (s)->d, (s)->n + (amt));                             \
+    if (!utstring_tmp) {                                   \
+      utstring_oom();                                      \
+    }                                                      \
+    (s)->d = utstring_tmp;                                 \
+    (s)->n += (amt);                                       \
+  }                                                        \
+} while(0)
+
+#define utstring_init(s)                                   \
+do {                                                       \
+  (s)->n = 0; (s)->i = 0; (s)->d = NULL;                   \
+  utstring_reserve(s,100);                                 \
+  (s)->d[0] = '\0';                                        \
+} while(0)
+
+#define utstring_done(s)                                   \
+do {                                                       \
+  if ((s)->d != NULL) __libc_free((s)->d);                 \
+  (s)->n = 0;                                              \
+} while(0)
+
+#define utstring_free(s)                                   \
+do {                                                       \
+  utstring_done(s);                                        \
+  __libc_free(s);                                          \
+} while(0)
+
+#define utstring_new(s)                                    \
+do {                                                       \
+  (s) = (UT_string*)__libc_malloc(sizeof(UT_string));             \
+  if (!(s)) {                                              \
+    utstring_oom();                                        \
+  }                                                        \
+  utstring_init(s);                                        \
+} while(0)
+
+#define utstring_renew(s)                                  \
+do {                                                       \
+   if (s) {                                                \
+     utstring_clear(s);                                    \
+   } else {                                                \
+     utstring_new(s);                                      \
+   }                                                       \
+} while(0)
+
+#define utstring_clear(s)                                  \
+do {                                                       \
+  (s)->i = 0;                                              \
+  (s)->d[0] = '\0';                                        \
+} while(0)
+
+#define utstring_bincpy(s,b,l)                             \
+do {                                                       \
+  utstring_reserve((s),(l)+1);                             \
+  if (l) memcpy(&(s)->d[(s)->i], b, l);                    \
+  (s)->i += (l);                                           \
+  (s)->d[(s)->i]='\0';                                     \
+} while(0)
+
+#define utstring_concat(dst,src)                                 \
+do {                                                             \
+  utstring_reserve((dst),((src)->i)+1);                          \
+  if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
+  (dst)->i += (src)->i;                                          \
+  (dst)->d[(dst)->i]='\0';                                       \
+} while(0)
+
+#define utstring_len(s) ((s)->i)
+
+#define utstring_body(s) ((s)->d)
+
+UTSTRING_UNUSED static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
+   int n;
+   va_list cp;
+   for (;;) {
+#ifdef _WIN32
+      cp = ap;
+#else
+      va_copy(cp, ap);
+#endif
+      n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
+      va_end(cp);
+
+      if ((n > -1) && ((size_t) n < (s->n-s->i))) {
+        s->i += n;
+        return;
+      }
+
+      /* Else try again with more space. */
+      if (n > -1) utstring_reserve(s,n+1); /* exact */
+      else utstring_reserve(s,(s->n)*2);   /* 2x */
+   }
+}
+#ifdef __GNUC__
+/* support printf format checking (2=the format string, 3=start of varargs) */
+static void utstring_printf(UT_string *s, const char *fmt, ...)
+  __attribute__ (( format( printf, 2, 3) ));
+#endif
+UTSTRING_UNUSED static void utstring_printf(UT_string *s, const char *fmt, ...) {
+   va_list ap;
+   va_start(ap,fmt);
+   utstring_printf_va(s,fmt,ap);
+   va_end(ap);
+}
+
+/*******************************************************************************
+ * begin substring search functions                                            *
+ ******************************************************************************/
+/* Build KMP table from left to right. */
+UTSTRING_UNUSED static void _utstring_BuildTable(
+    const char *P_Needle,
+    size_t P_NeedleLen,
+    long *P_KMP_Table)
+{
+    long i, j;
+
+    i = 0;
+    j = i - 1;
+    P_KMP_Table[i] = j;
+    while (i < (long) P_NeedleLen)
+    {
+        while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
+        {
+           j = P_KMP_Table[j];
+        }
+        i++;
+        j++;
+        if (i < (long) P_NeedleLen)
+        {
+            if (P_Needle[i] == P_Needle[j])
+            {
+                P_KMP_Table[i] = P_KMP_Table[j];
+            }
+            else
+            {
+                P_KMP_Table[i] = j;
+            }
+        }
+        else
+        {
+            P_KMP_Table[i] = j;
+        }
+    }
+
+    return;
+}
+
+
+/* Build KMP table from right to left. */
+UTSTRING_UNUSED static void _utstring_BuildTableR(
+    const char *P_Needle,
+    size_t P_NeedleLen,
+    long *P_KMP_Table)
+{
+    long i, j;
+
+    i = P_NeedleLen - 1;
+    j = i + 1;
+    P_KMP_Table[i + 1] = j;
+    while (i >= 0)
+    {
+        while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
+        {
+           j = P_KMP_Table[j + 1];
+        }
+        i--;
+        j--;
+        if (i >= 0)
+        {
+            if (P_Needle[i] == P_Needle[j])
+            {
+                P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
+            }
+            else
+            {
+                P_KMP_Table[i + 1] = j;
+            }
+        }
+        else
+        {
+            P_KMP_Table[i + 1] = j;
+        }
+    }
+
+    return;
+}
+
+
+/* Search data from left to right. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_find(
+    const char *P_Haystack,
+    size_t P_HaystackLen,
+    const char *P_Needle,
+    size_t P_NeedleLen,
+    long *P_KMP_Table)
+{
+    long i, j;
+    long V_FindPosition = -1;
+
+    /* Search from left to right. */
+    i = j = 0;
+    while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
+    {
+        while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
+        {
+            i = P_KMP_Table[i];
+        }
+        i++;
+        j++;
+        if (i >= (int)P_NeedleLen)
+        {
+            /* Found. */
+            V_FindPosition = j - i;
+            break;
+        }
+    }
+
+    return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_findR(
+    const char *P_Haystack,
+    size_t P_HaystackLen,
+    const char *P_Needle,
+    size_t P_NeedleLen,
+    long *P_KMP_Table)
+{
+    long i, j;
+    long V_FindPosition = -1;
+
+    /* Search from right to left. */
+    j = (P_HaystackLen - 1);
+    i = (P_NeedleLen - 1);
+    while ( (j >= 0) && (j >= i) )
+    {
+        while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
+        {
+            i = P_KMP_Table[i + 1];
+        }
+        i--;
+        j--;
+        if (i < 0)
+        {
+            /* Found. */
+            V_FindPosition = j + 1;
+            break;
+        }
+    }
+
+    return V_FindPosition;
+}
+
+
+/* Search data from left to right. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_find(
+    UT_string *s,
+    long P_StartPosition,   /* Start from 0. -1 means last position. */
+    const char *P_Needle,
+    size_t P_NeedleLen)
+{
+    long V_StartPosition;
+    long V_HaystackLen;
+    long *V_KMP_Table;
+    long V_FindPosition = -1;
+
+    if (P_StartPosition < 0)
+    {
+        V_StartPosition = s->i + P_StartPosition;
+    }
+    else
+    {
+        V_StartPosition = P_StartPosition;
+    }
+    V_HaystackLen = s->i - V_StartPosition;
+    if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+    {
+        V_KMP_Table = (long *)__libc_malloc(sizeof(long) * (P_NeedleLen + 1));
+        if (V_KMP_Table != NULL)
+        {
+            _utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
+
+            V_FindPosition = _utstring_find(s->d + V_StartPosition,
+                                            V_HaystackLen,
+                                            P_Needle,
+                                            P_NeedleLen,
+                                            V_KMP_Table);
+            if (V_FindPosition >= 0)
+            {
+                V_FindPosition += V_StartPosition;
+            }
+
+            __libc_free(V_KMP_Table);
+        }
+    }
+
+    return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_findR(
+    UT_string *s,
+    long P_StartPosition,   /* Start from 0. -1 means last position. */
+    const char *P_Needle,
+    size_t P_NeedleLen)
+{
+    long V_StartPosition;
+    long V_HaystackLen;
+    long *V_KMP_Table;
+    long V_FindPosition = -1;
+
+    if (P_StartPosition < 0)
+    {
+        V_StartPosition = s->i + P_StartPosition;
+    }
+    else
+    {
+        V_StartPosition = P_StartPosition;
+    }
+    V_HaystackLen = V_StartPosition + 1;
+    if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+    {
+        V_KMP_Table = (long *)__libc_malloc(sizeof(long) * (P_NeedleLen + 1));
+        if (V_KMP_Table != NULL)
+        {
+            _utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
+
+            V_FindPosition = _utstring_findR(s->d,
+                                             V_HaystackLen,
+                                             P_Needle,
+                                             P_NeedleLen,
+                                             V_KMP_Table);
+
+            __libc_free(V_KMP_Table);
+        }
+    }
+
+    return V_FindPosition;
+}
+/*******************************************************************************
+ * end substring search functions                                              *
+ ******************************************************************************/
+
+#endif /* UTSTRING_H */


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