Allow replacing the GSlice allocator with jemalloc

The jemalloc allocator implementation comes from the BSD world, and it's
used in various places, including:

 * Mozilla
 * Rust
 * Android
 * Cassandra
 * MariaDB
 * Redis

More info: http://jemalloc.net

Currently, support is enabled by default on Linux, but you have to opt
in using `G_SLICE=use-jemalloc`.
diff --git a/configure.ac b/configure.ac
index 823c45b..bc3e064 100644
--- a/configure.ac
+++ b/configure.ac
@@ -130,6 +130,7 @@
     glib_dir_separator='\\\\'
     glib_searchpath_separator=';'
     glib_cv_stack_grows=no
+    glib_jemalloc_default=no
     # Unfortunately the mingw implementations of C99-style snprintf and vsnprintf
     # don't seem to be quite good enough, at least not in mingw-runtime-3.14.
     # (Sorry, I don't know exactly what is the problem, but it is related to
@@ -155,6 +156,7 @@
     glib_pollfd_format='%d'
     glib_dir_separator='/'
     glib_searchpath_separator=':'
+    glib_jemalloc_default=yes
     ;;
 esac
 case $host in
@@ -256,6 +258,10 @@
               [AS_HELP_STRING([--disable-mem-pools],
 			      [disable all glib memory pools])],,
 	      [disable_mem_pools=no])
+AC_ARG_ENABLE(jemalloc,
+              [AS_HELP_STRING([--disable-jemalloc],
+                              [disable jemalloc])],,
+              [enable_jemalloc="$glib_jemalloc_default"])
 
 GLIB_TESTS
 
@@ -273,6 +279,24 @@
   AC_MSG_RESULT([yes])
 ])
 
+PKG_CHECK_MODULES(JEMALLOC, [jemalloc], [has_jemalloc=yes], [has_jemalloc=no])
+AC_MSG_CHECKING([whether jemalloc should be used in GSlice])
+AS_IF([test "x$enable_jemalloc" = xyes], [
+  AS_IF([test "x$has_jemalloc" = xyes], [
+    AC_DEFINE(ENABLE_JEMALLOC, 1, [Whether to enable jemalloc as the GSlice allocator])
+    AC_MSG_RESULT([yes])
+    JEMALLOC_REQUIRES="jemalloc"
+  ], [
+    AC_MSG_ERROR([jemalloc is not available])
+  ])
+], [
+  AC_MSG_RESULT([no])
+  JEMALLOC_REQUIRES=""
+])
+AC_SUBST(JEMALLOC_CFLAGS)
+AC_SUBST(JEMALLOC_LIBS)
+AC_SUBST(JEMALLOC_REQUIRES)
+
 dnl location to install runtime libraries, e.g. ../../lib to install
 dnl to /lib if libdir is /usr/lib
 AC_ARG_WITH(runtime-libdir,
diff --git a/glib-2.0.pc.in b/glib-2.0.pc.in
index 275fc01..e9ffabf 100644
--- a/glib-2.0.pc.in
+++ b/glib-2.0.pc.in
@@ -10,7 +10,7 @@
 Name: GLib
 Description: C Utility Library
 Version: @VERSION@
-Requires.private: @PCRE_REQUIRES@
+Requires.private: @PCRE_REQUIRES@ @JEMALLOC_REQUIRES@
 Libs: -L${libdir} -lglib-2.0 @INTLLIBS@
 Libs.private: @G_THREAD_LIBS@ @G_LIBS_EXTRA@ @PCRE_LIBS@ @INTLLIBS@ @ICONV_LIBS@ @CARBON_LIBS@ @COCOA_LIBS@
 Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include @GLIB_EXTRA_CFLAGS@
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 8da549c..ae25ced 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -351,8 +351,8 @@
 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) $(JEMALLOC_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) $(JEMALLOC_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/gslice.c b/glib/gslice.c
index 6b0f226..87330a4 100644
--- a/glib/gslice.c
+++ b/glib/gslice.c
@@ -19,6 +19,12 @@
 #include "config.h"
 #include "glibconfig.h"
 
+#ifdef ENABLE_JEMALLOC
+/* Do not demangle; it's cleaner to use the je_* API */
+# define JEMALLOC_NO_DEMANGLE
+# include <jemalloc/jemalloc.h>
+#endif
+
 #if     defined HAVE_POSIX_MEMALIGN && defined POSIX_MEMALIGN_WITH_COMPLIANT_ALLOCS
 #  define HAVE_COMPLIANT_POSIX_MEMALIGN 1
 #endif
@@ -238,6 +244,7 @@
 } ThreadMemory;
 typedef struct {
   gboolean always_malloc;
+  gboolean use_jemalloc;
   gboolean bypass_magazines;
   gboolean debug_blocks;
   gsize    working_set_msecs;
@@ -286,6 +293,7 @@
 static Allocator   allocator[1] = { { 0, }, };
 static SliceConfig slice_config = {
   FALSE,        /* always_malloc */
+  FALSE,        /* use_jemalloc */
   FALSE,        /* bypass_magazines */
   FALSE,        /* debug_blocks */
   15 * 1000,    /* working_set_msecs */
@@ -304,6 +312,9 @@
     case G_SLICE_CONFIG_ALWAYS_MALLOC:
       slice_config.always_malloc = value != 0;
       break;
+    case G_SLICE_CONFIG_USE_JEMALLOC:
+      slice_config.use_jemalloc = value != 0;
+      break;
     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
       slice_config.bypass_magazines = value != 0;
       break;
@@ -323,6 +334,8 @@
     {
     case G_SLICE_CONFIG_ALWAYS_MALLOC:
       return slice_config.always_malloc;
+    case G_SLICE_CONFIG_USE_JEMALLOC:
+      return slice_config.use_jemalloc;
     case G_SLICE_CONFIG_BYPASS_MAGAZINES:
       return slice_config.bypass_magazines;
     case G_SLICE_CONFIG_WORKING_SET_MSECS:
@@ -372,6 +385,9 @@
       const GDebugKey keys[] = {
         { "always-malloc", 1 << 0 },
         { "debug-blocks",  1 << 1 },
+#ifdef ENABLE_JEMALLOC
+        { "use-jemalloc",  1 << 2 },
+#endif
       };
 
       flags = g_parse_debug_string (val, keys, G_N_ELEMENTS (keys));
@@ -379,6 +395,8 @@
         config->always_malloc = TRUE;
       if (flags & (1 << 1))
         config->debug_blocks = TRUE;
+      if (flags & (1 << 2))
+        config->use_jemalloc = TRUE;
     }
   else
     {
@@ -389,7 +407,10 @@
        * valgrind just by setting G_SLICE to the empty string.
        */
       if (RUNNING_ON_VALGRIND)
-        config->always_malloc = TRUE;
+        {
+          config->always_malloc = TRUE;
+          config->use_jemalloc = FALSE;
+        }
     }
 }
 
@@ -429,7 +450,7 @@
   /* we can only align to system page size */
   allocator->max_page_size = sys_page_size;
 #endif
-  if (allocator->config.always_malloc)
+  if (allocator->config.always_malloc || allocator->config.use_jemalloc)
     {
       allocator->contention_counters = NULL;
       allocator->magazines = NULL;
@@ -449,7 +470,7 @@
   magazine_cache_update_stamp();
   /* values cached for performance reasons */
   allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator);
-  if (allocator->config.always_malloc || allocator->config.bypass_magazines)
+  if (allocator->config.always_malloc || allocator->config.bypass_magazines || allocator->config.use_jemalloc)
     allocator->max_slab_chunk_size_for_magazine_cache = 0;      /* non-optimized cases */
 }
 
@@ -460,6 +481,9 @@
   if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache))
     return 1;           /* use magazine cache */
 
+  if (allocator->config.use_jemalloc)
+    return 3;
+
   if (!allocator->config.always_malloc &&
       aligned_chunk_size &&
       aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator))
@@ -1021,6 +1045,12 @@
       mem = slab_allocator_alloc_chunk (chunk_size);
       g_mutex_unlock (&allocator->slab_mutex);
     }
+#ifdef ENABLE_JEMALLOC
+  else if (acat == 3)
+    {
+      mem = je_malloc (mem_size);
+    }
+#endif
   else                          /* delegate to system malloc */
     mem = g_malloc (mem_size);
   if (G_UNLIKELY (allocator->config.debug_blocks))
@@ -1129,6 +1159,14 @@
       slab_allocator_free_chunk (chunk_size, mem_block);
       g_mutex_unlock (&allocator->slab_mutex);
     }
+#ifdef ENABLE_JEMALLOC
+  else if (acat == 3)
+    {
+      if (G_UNLIKELY (g_mem_gc_friendly))
+        memset (mem_block, 0, mem_size);
+      je_free (mem_block);
+    }
+#endif
   else                                  /* delegate to system malloc */
     {
       if (G_UNLIKELY (g_mem_gc_friendly))
@@ -1219,18 +1257,37 @@
         }
       g_mutex_unlock (&allocator->slab_mutex);
     }
-  else                                  /* delegate to system malloc */
-    while (slice)
-      {
-        guint8 *current = slice;
-        slice = *(gpointer*) (current + next_offset);
-        if (G_UNLIKELY (allocator->config.debug_blocks) &&
-            !smc_notify_free (current, mem_size))
-          abort();
-        if (G_UNLIKELY (g_mem_gc_friendly))
-          memset (current, 0, mem_size);
-        g_free (current);
-      }
+#ifdef ENABLE_JEMALLOC
+  else if (acat == 3)
+    {
+      while (slice)
+        {
+          guint8 *current = slice;
+          slice = *(gpointer*) (current + next_offset);
+          if (G_UNLIKELY (allocator->config.debug_blocks) &&
+              !smc_notify_free (current, mem_size))
+            abort();
+          if (G_UNLIKELY (g_mem_gc_friendly))
+            memset (current, 0, mem_size);
+          je_free (current);
+        }
+    }
+#endif
+  else
+    {
+      /* delegate to system malloc */
+      while (slice)
+        {
+          guint8 *current = slice;
+          slice = *(gpointer*) (current + next_offset);
+          if (G_UNLIKELY (allocator->config.debug_blocks) &&
+              !smc_notify_free (current, mem_size))
+            abort();
+          if (G_UNLIKELY (g_mem_gc_friendly))
+            memset (current, 0, mem_size);
+          g_free (current);
+        }
+    }
 }
 
 /* --- single page allocator --- */
diff --git a/glib/gslice.h b/glib/gslice.h
index 8076276..3b1a9cb 100644
--- a/glib/gslice.h
+++ b/glib/gslice.h
@@ -79,7 +79,8 @@
   G_SLICE_CONFIG_WORKING_SET_MSECS,
   G_SLICE_CONFIG_COLOR_INCREMENT,
   G_SLICE_CONFIG_CHUNK_SIZES,
-  G_SLICE_CONFIG_CONTENTION_COUNTER
+  G_SLICE_CONFIG_CONTENTION_COUNTER,
+  G_SLICE_CONFIG_USE_JEMALLOC
 } GSliceConfig;
 
 GLIB_DEPRECATED_IN_2_34