Backing out bug 427109 to try to fix Linux bustage
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 24 Jun 2008 19:52:40 +1200
changeset 15486 2cbe07bd5857f8c029102d736f2e8077f3011f7a
parent 15485 9756a45384e63429723dafb1604452995a4d33b1
child 15487 0c8e64474660b88012e19cd67b158cfb5dc1415d
child 15492 76caed42cf7cb7098aa0eb58242dd36054d06865
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs427109
milestone1.9.1a1pre
Backing out bug 427109 to try to fix Linux bustage
memory/jemalloc/Makefile.in
memory/jemalloc/jemalloc.c
memory/jemalloc/jemalloc.h
--- a/memory/jemalloc/Makefile.in
+++ b/memory/jemalloc/Makefile.in
@@ -68,19 +68,18 @@ libs:: $(CRT_OBJ_DIR)/build/intel/mozcrt
 	done
 	# truly awful
 	#XXX: get ed into mozillabuild, bug 415123
 	$(PERL) $(srcdir)/apply-ed-patches.pl $(srcdir)/crtsp1.diff \
 	$(CRT_OBJ_DIR) $(srcdir)/ed.exe
 
 $(CRT_OBJ_DIR)/build/intel/mozcrt19.dll: \
   $(CRT_OBJ_DIR)/jemalloc.c $(srcdir)/jemalloc.c $(srcdir)/jemalloc.h \
-  $(srcdir)/ql.h $(srcdir)/qr.h $(srcdir)/rb.h
-	cp $(srcdir)/jemalloc.c $(srcdir)/jemalloc.h $(srcdir)/ql.h \
-	$(srcdir)/qr.h $(srcdir)/rb.h $(CRT_OBJ_DIR)
+  $(srcdir)/rb.h
+	cp $(srcdir)/jemalloc.c $(srcdir)/jemalloc.h $(srcdir)/rb.h $(CRT_OBJ_DIR)
 # this pretty much sucks, but nmake and make don't play well together
 	$(PYTHON) $(srcdir)/build-crt.py $(CRT_OBJ_DIR)
 	#XXX: these don't link right for some reason
 	rm $(CRT_OBJ_DIR)/build/intel/{libcmt,libcpmt}.lib
 else
 # Using a pre-built DLL, so just install it.
 libs:: $(WIN32_CUSTOM_CRT_DIR)/mozcrt19.dll
 	$(INSTALL) $< $(FINAL_TARGET)
--- a/memory/jemalloc/jemalloc.c
+++ b/memory/jemalloc/jemalloc.c
@@ -1,10 +1,9 @@
-/* -*- Mode: C; tab-width: 8; c-basic-offset: 8 -*- */
-/* vim:set softtabstop=8 shiftwidth=8: */
+/* -*- Mode: C; tab-width: 4; c-basic-offset: 4 -*- */
 /*-
  * Copyright (C) 2006-2008 Jason Evans <jasone@FreeBSD.org>.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
@@ -158,32 +157,41 @@
 
 /*
  * MALLOC_BALANCE enables monitoring of arena lock contention and dynamically
  * re-balances arena load if exponentially averaged contention exceeds a
  * certain threshold.
  */
 /* #define	MALLOC_BALANCE */
 
-#if (!defined(MOZ_MEMORY_WINDOWS) && !defined(MOZ_MEMORY_DARWIN))
-   /*
-    * MALLOC_PAGEFILE causes all mmap()ed memory to be backed by temporary
-    * files, so that if a chunk is mapped, it is guaranteed to be swappable.
-    * This avoids asynchronous OOM failures that are due to VM over-commit.
-    *
-    * XXX OS X over-commits, so we should probably use mmap() instead of
-    * vm_allocate(), so that MALLOC_PAGEFILE works.
-    */
-#  define MALLOC_PAGEFILE
-#endif
-
-#ifdef MALLOC_PAGEFILE
-/* Write size when initializing a page file. */
-#  define MALLOC_PAGEFILE_WRITE_SIZE 512
-#endif
+/*
+ * MALLOC_DSS enables use of sbrk(2) to allocate chunks from the data storage
+ * segment (DSS).  In an ideal world, this functionality would be completely
+ * unnecessary, but we are burdened by history and the lack of resource limits
+ * for anonymous mapped memory.
+ */
+/*
+ * Uniformly disable sbrk(2) use in Mozilla, since it has various problems
+ * across platforms:
+ *
+ *   Linux: sbrk() fails to detect error conditions when using large amounts of
+ *          memory, resulting in memory corruption.
+ *
+ *   Darwin: sbrk() is severely limited in how much memory it can allocate, and
+ *           its use is strongly discouraged.
+ *
+ *   Solaris: sbrk() does not necessarily discard pages when the DSS is shrunk,
+ *            which makes it possible to get non-zeroed pages when re-expanding
+ *            the DSS.  This is incompatible with jemalloc's assumptions, and a
+ *            fix would require chunk_alloc_dss() to optionally zero memory as
+ *            chunk_recycle_dss() does (though the cost could be reduced by
+ *            keeping track of the DSS high water mark and zeroing only when
+ *            below that mark).
+ */
+/* #define	MALLOC_DSS */
 
 #ifdef MOZ_MEMORY_LINUX
 #define	_GNU_SOURCE /* For mremap(2). */
 #define	issetugid() 0
 #if 0 /* Enable in order to test decommit code on Linux. */
 #  define MALLOC_DECOMMIT
 #endif
 #endif
@@ -272,19 +280,16 @@ typedef long ssize_t;
 #endif
 #include "spinlock.h"
 #include "namespace.h"
 #endif
 #include <sys/mman.h>
 #ifndef MADV_FREE
 #  define MADV_FREE	MADV_DONTNEED
 #endif
-#ifndef MAP_NOSYNC
-#  define MAP_NOSYNC	0
-#endif
 #include <sys/param.h>
 #ifndef MOZ_MEMORY
 #include <sys/stddef.h>
 #endif
 #include <sys/time.h>
 #include <sys/types.h>
 #ifndef MOZ_MEMORY_SOLARIS
 #include <sys/sysctl.h>
@@ -353,18 +358,16 @@ static const bool __isthreaded = true;
 #  ifndef NDEBUG
 #    define NDEBUG
 #  endif
 #endif
 #ifndef MOZ_MEMORY_WINDOWS
 #include <assert.h>
 #endif
 
-#include "qr.h"
-#include "ql.h"
 #ifdef MOZ_MEMORY_WINDOWS
    /* MSVC++ does not support C99 variable-length arrays. */
 #  define RB_NO_C99_VARARRAYS
 #endif
 #include "rb.h"
 
 #ifdef MALLOC_DEBUG
    /* Disable inlining to make debugging easier. */
@@ -450,28 +453,16 @@ static const bool __isthreaded = true;
  * Size and alignment of memory chunks that are allocated by the OS's virtual
  * memory system.
  */
 #define	CHUNK_2POW_DEFAULT	20
 
 /* Maximum number of dirty pages per arena. */
 #define	DIRTY_MAX_DEFAULT	(1U << 10)
 
-/* Default reserve chunks. */
-#define	RESERVE_MIN_2POW_DEFAULT	1
-/*
- * Default range (in chunks) between reserve_min and reserve_max, in addition
- * to the mandatory one chunk per arena.
- */
-#ifdef MALLOC_PAGEFILE
-#  define RESERVE_RANGE_2POW_DEFAULT	5
-#else
-#  define RESERVE_RANGE_2POW_DEFAULT	0
-#endif
-
 /*
  * Maximum size of L1 cache line.  This is used to avoid cache line aliasing,
  * so over-estimates are okay (up to a point), but under-estimates will
  * negatively affect performance.
  */
 #define	CACHELINE_2POW		6
 #define	CACHELINE		((size_t)(1U << CACHELINE_2POW))
 
@@ -705,40 +696,16 @@ struct extent_node_s {
 
 	/* Total region size. */
 	size_t	size;
 };
 typedef rb_tree(extent_node_t) extent_tree_t;
 
 /******************************************************************************/
 /*
- * Reserve data structures.
- */
-
-/* Callback registration. */
-typedef struct reserve_reg_s reserve_reg_t;
-struct reserve_reg_s {
-	/* Linkage for list of all registered callbacks. */
-	ql_elm(reserve_reg_t)	link;
-
-	/* Callback function pointer. */
-	reserve_cb_t		*cb;
-
-	/* Opaque application data pointer. */
-	void			*ctx;
-
-	/*
-	 * Sequence number of most condition notification most recently sent to
-	 * this callback.
-	 */
-	uint64_t		seq;
-};
-
-/******************************************************************************/
-/*
  * Arena data structures.
  */
 
 typedef struct arena_s arena_t;
 typedef struct arena_bin_s arena_bin_t;
 
 /*
  * Each map element contains several flags, plus page position for runs that
@@ -865,38 +832,44 @@ struct arena_s {
 #else
 	pthread_mutex_t		lock;
 #endif
 
 #ifdef MALLOC_STATS
 	arena_stats_t		stats;
 #endif
 
-	/*
-	 * Chunk allocation sequence number, used to detect races with other
-	 * threads during chunk allocation, and then discard unnecessary chunks.
-	 */
-	uint64_t		chunk_seq;
-
 	/* Tree of all chunks this arena manages. */
 	arena_chunk_tree_t	chunks_all;
 
 	/*
 	 * Tree of dirty-page-containing chunks this arena manages.  This tree
 	 * is maintained in addition to chunks_all in order to make
 	 * deallocation O(lg d), where 'd' is the size of chunks_dirty.
 	 *
 	 * Without this tree, deallocation would be O(a), where 'a' is the size
 	 * of chunks_all.  Since dirty pages are purged in descending memory
 	 * order, it would not be difficult to trigger something approaching
 	 * worst case behavior with a series of large deallocations.
 	 */
 	arena_chunk_tree_t	chunks_dirty;
 
 	/*
+	 * In order to avoid rapid chunk allocation/deallocation when an arena
+	 * oscillates right on the cusp of needing a new chunk, cache the most
+	 * recently freed chunk.  The spare is left in the arena's chunk trees
+	 * until it is deleted.
+	 *
+	 * There is one spare chunk per arena, rather than one spare total, in
+	 * order to avoid interactions between multiple threads that could make
+	 * a single spare inadequate.
+	 */
+	arena_chunk_t		*spare;
+
+	/*
 	 * Current count of pages within unused runs that are potentially
 	 * dirty, and for which madvise(... MADV_FREE) has not been called.  By
 	 * tracking this, we can institute a limit on how much dirty unused
 	 * memory is mapped for each arena.
 	 */
 	size_t			ndirty;
 
 	/*
@@ -982,60 +955,46 @@ static size_t		arena_maxclass; /* Max si
  */
 
 /* Protects chunk-related data structures. */
 static malloc_mutex_t	huge_mtx;
 
 /* Tree of chunks that are stand-alone huge allocations. */
 static extent_tree_t	huge;
 
+#ifdef MALLOC_DSS
+/*
+ * Protects sbrk() calls.  This avoids malloc races among threads, though it
+ * does not protect against races with threads that call sbrk() directly.
+ */
+static malloc_mutex_t	dss_mtx;
+/* Base address of the DSS. */
+static void		*dss_base;
+/* Current end of the DSS, or ((void *)-1) if the DSS is exhausted. */
+static void		*dss_prev;
+/* Current upper limit on DSS addresses. */
+static void		*dss_max;
+
+/*
+ * Trees of chunks that were previously allocated (trees differ only in node
+ * ordering).  These are used when allocating chunks, in an attempt to re-use
+ * address space.  Depending on function, different tree orderings are needed,
+ * which is why there are two trees with the same contents.
+ */
+static extent_tree_t	dss_chunks_szad;
+static extent_tree_t	dss_chunks_ad;
+#endif
+
 #ifdef MALLOC_STATS
 /* Huge allocation statistics. */
 static uint64_t		huge_nmalloc;
 static uint64_t		huge_ndalloc;
 static size_t		huge_allocated;
 #endif
 
-/****************/
-/*
- * Memory reserve.
- */
-
-#ifdef MALLOC_PAGEFILE
-static char		pagefile_templ[PATH_MAX];
-#endif
-
-/* Protects reserve-related data structures. */
-static malloc_mutex_t	reserve_mtx;
-
-/*
- * Bounds on acceptable reserve size, and current reserve size.  Reserve
- * depletion may cause (reserve_cur < reserve_min).
- */
-static size_t		reserve_min;
-static size_t		reserve_cur;
-static size_t		reserve_max;
-
-/* List of registered callbacks. */
-static ql_head(reserve_reg_t) reserve_regs;
-
-/*
- * Condition notification sequence number, used to determine whether all
- * registered callbacks have been notified of the most current condition.
- */
-static uint64_t		reserve_seq;
-
-/*
- * Trees of chunks currently in the memory reserve.  Depending on function,
- * different tree orderings are needed, which is why there are two trees with
- * the same contents.
- */
-static extent_tree_t	reserve_chunks_szad;
-static extent_tree_t	reserve_chunks_ad;
-
 /****************************/
 /*
  * base (internal allocation).
  */
 
 /*
  * Current pages that are being used for internal memory allocations.  These
  * pages are carved up in cacheline-size quanta, so that there is no chance of
@@ -1043,34 +1002,32 @@ static extent_tree_t	reserve_chunks_ad;
  */
 static void		*base_pages;
 static void		*base_next_addr;
 #ifdef MALLOC_DECOMMIT
 static void		*base_next_decommitted;
 #endif
 static void		*base_past_addr; /* Addr immediately past base_pages. */
 static extent_node_t	*base_nodes;
-static reserve_reg_t	*base_reserve_regs;
 static malloc_mutex_t	base_mtx;
 #ifdef MALLOC_STATS
 static size_t		base_mapped;
 #endif
 
 /********/
 /*
  * Arenas.
  */
 
 /*
  * Arenas that are used to service external requests.  Not all elements of the
  * arenas array are necessarily used; arenas are created lazily as needed.
  */
 static arena_t		**arenas;
 static unsigned		narenas;
-static unsigned		narenas_2pow;
 #ifndef NO_TLS
 #  ifdef MALLOC_BALANCE
 static unsigned		narenas_2pow;
 #  else
 static unsigned		next_arena;
 #  endif
 #endif
 #ifdef MOZ_MEMORY
@@ -1106,29 +1063,28 @@ static bool	opt_abort = true;
 static bool	opt_junk = true;
 #endif
 #else
 static bool	opt_abort = false;
 #ifdef MALLOC_FILL
 static bool	opt_junk = false;
 #endif
 #endif
+#ifdef MALLOC_DSS
+static bool	opt_dss = true;
+static bool	opt_mmap = true;
+#endif
 static size_t	opt_dirty_max = DIRTY_MAX_DEFAULT;
 #ifdef MALLOC_BALANCE
 static uint64_t	opt_balance_threshold = BALANCE_THRESHOLD_DEFAULT;
 #endif
 static bool	opt_print_stats = false;
 static size_t	opt_quantum_2pow = QUANTUM_2POW_MIN;
 static size_t	opt_small_max_2pow = SMALL_MAX_2POW_DEFAULT;
 static size_t	opt_chunk_2pow = CHUNK_2POW_DEFAULT;
-static int	opt_reserve_min_lshift = 0;
-static int	opt_reserve_range_lshift = 0;
-#ifdef MALLOC_PAGEFILE
-static bool	opt_pagefile = true;
-#endif
 #ifdef MALLOC_UTRACE
 static bool	opt_utrace = false;
 #endif
 #ifdef MALLOC_SYSV
 static bool	opt_sysv = false;
 #endif
 #ifdef MALLOC_XMALLOC
 static bool	opt_xmalloc = false;
@@ -1169,48 +1125,51 @@ static void	wrtmessage(const char *p1, c
 #ifdef MALLOC_STATS
 #ifdef MOZ_MEMORY_DARWIN
 /* Avoid namespace collision with OS X's malloc APIs. */
 #define malloc_printf moz_malloc_printf
 #endif
 static void	malloc_printf(const char *format, ...);
 #endif
 static char	*umax2s(uintmax_t x, char *s);
+#ifdef MALLOC_DSS
+static bool	base_pages_alloc_dss(size_t minsize);
+#endif
 static bool	base_pages_alloc_mmap(size_t minsize);
 static bool	base_pages_alloc(size_t minsize);
 static void	*base_alloc(size_t size);
 static void	*base_calloc(size_t number, size_t size);
 static extent_node_t *base_node_alloc(void);
 static void	base_node_dealloc(extent_node_t *node);
-static reserve_reg_t *base_reserve_reg_alloc(void);
-static void	base_reserve_reg_dealloc(reserve_reg_t *reg);
 #ifdef MALLOC_STATS
 static void	stats_print(arena_t *arena);
 #endif
-static void	*pages_map(void *addr, size_t size, int pfd);
+static void	*pages_map(void *addr, size_t size);
 static void	pages_unmap(void *addr, size_t size);
-static void	*chunk_alloc_mmap(size_t size, bool pagefile);
-#ifdef MALLOC_PAGEFILE
-static int	pagefile_init(size_t size);
-static void	pagefile_close(int pfd);
-#endif
-static void	*chunk_recycle_reserve(size_t size, bool zero);
-static void	*chunk_alloc(size_t size, bool zero, bool pagefile);
-static extent_node_t *chunk_dealloc_reserve(void *chunk, size_t size);
+#ifdef MALLOC_DSS
+static void	*chunk_alloc_dss(size_t size);
+static void	*chunk_recycle_dss(size_t size, bool zero);
+#endif
+static void	*chunk_alloc_mmap(size_t size);
+static void	*chunk_alloc(size_t size, bool zero);
+#ifdef MALLOC_DSS
+static extent_node_t *chunk_dealloc_dss_record(void *chunk, size_t size);
+static bool	chunk_dealloc_dss(void *chunk, size_t size);
+#endif
 static void	chunk_dealloc_mmap(void *chunk, size_t size);
 static void	chunk_dealloc(void *chunk, size_t size);
 #ifndef NO_TLS
 static arena_t	*choose_arena_hard(void);
 #endif
 static extent_node_t *arena_chunk_node_alloc(arena_chunk_t *chunk);
 static void	arena_chunk_node_dealloc(arena_chunk_t *chunk,
     extent_node_t *node);
 static void	arena_run_split(arena_t *arena, arena_run_t *run, size_t size,
     bool small, bool zero);
-static void arena_chunk_init(arena_t *arena, arena_chunk_t *chunk);
+static arena_chunk_t *arena_chunk_alloc(arena_t *arena);
 static void	arena_chunk_dealloc(arena_t *arena, arena_chunk_t *chunk);
 static arena_run_t *arena_run_alloc(arena_t *arena, size_t size, bool small,
     bool zero);
 static void	arena_purge(arena_t *arena);
 static void	arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty);
 static void	arena_run_trim_head(arena_t *arena, arena_chunk_t *chunk,
     extent_node_t *nodeB, arena_run_t *run, size_t oldsize, size_t newsize);
 static void	arena_run_trim_tail(arena_t *arena, arena_chunk_t *chunk,
@@ -1240,20 +1199,16 @@ static void	*huge_malloc(size_t size, bo
 static void	*huge_palloc(size_t alignment, size_t size);
 static void	*huge_ralloc(void *ptr, size_t size, size_t oldsize);
 static void	huge_dalloc(void *ptr);
 static void	malloc_print_stats(void);
 #ifndef MOZ_MEMORY_WINDOWS
 static
 #endif
 bool		malloc_init_hard(void);
-static void	reserve_shrink(void);
-static uint64_t	reserve_notify(reserve_cnd_t cnd, size_t size, uint64_t seq);
-static uint64_t	reserve_crit(size_t size, const char *fname, uint64_t seq);
-static void	reserve_fail(size_t size, const char *fname);
 
 /*
  * End function prototypes.
  */
 /******************************************************************************/
 /*
  * Begin mutex.  We can't use normal pthread mutexes in all places, because
  * they require malloc()ed memory, which causes bootstrapping issues in some
@@ -1690,72 +1645,114 @@ pages_commit(void *addr, size_t size)
 #  else
 	if (mmap(addr, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE |
 	    MAP_ANON, -1, 0) == MAP_FAILED)
 		abort();
 #  endif
 }
 #endif
 
+#ifdef MALLOC_DSS
+static bool
+base_pages_alloc_dss(size_t minsize)
+{
+
+	/*
+	 * Do special DSS allocation here, since base allocations don't need to
+	 * be chunk-aligned.
+	 */
+	malloc_mutex_lock(&dss_mtx);
+	if (dss_prev != (void *)-1) {
+		intptr_t incr;
+		size_t csize = CHUNK_CEILING(minsize);
+
+		do {
+			/* Get the current end of the DSS. */
+			dss_max = sbrk(0);
+
+			/*
+			 * Calculate how much padding is necessary to
+			 * chunk-align the end of the DSS.  Don't worry about
+			 * dss_max not being chunk-aligned though.
+			 */
+			incr = (intptr_t)chunksize
+			    - (intptr_t)CHUNK_ADDR2OFFSET(dss_max);
+			assert(incr >= 0);
+			if ((size_t)incr < minsize)
+				incr += csize;
+
+			dss_prev = sbrk(incr);
+			if (dss_prev == dss_max) {
+				/* Success. */
+				dss_max = (void *)((intptr_t)dss_prev + incr);
+				base_pages = dss_prev;
+				base_next_addr = base_pages;
+				base_past_addr = dss_max;
+#ifdef MALLOC_STATS
+				base_mapped += incr;
+#endif
+				malloc_mutex_unlock(&dss_mtx);
+				return (false);
+			}
+		} while (dss_prev != (void *)-1);
+	}
+	malloc_mutex_unlock(&dss_mtx);
+
+	return (true);
+}
+#endif
+
 static bool
 base_pages_alloc_mmap(size_t minsize)
 {
-	bool ret;
 	size_t csize;
 #ifdef MALLOC_DECOMMIT
 	size_t pminsize;
 #endif
-	int pfd;
 
 	assert(minsize != 0);
-	csize = CHUNK_CEILING(minsize);
-#ifdef MALLOC_PAGEFILE
-	if (opt_pagefile) {
-		pfd = pagefile_init(csize);
-		if (pfd == -1)
-			return (true);
-	} else
-#endif
-		pfd = -1;
-	base_pages = pages_map(NULL, csize, pfd);
-	if (base_pages == NULL) {
-		ret = true;
-		goto RETURN;
-	}
+	csize = PAGE_CEILING(minsize);
+	base_pages = pages_map(NULL, csize);
+	if (base_pages == NULL)
+		return (true);
 	base_next_addr = base_pages;
 	base_past_addr = (void *)((uintptr_t)base_pages + csize);
 #ifdef MALLOC_DECOMMIT
 	/*
 	 * Leave enough pages for minsize committed, since otherwise they would
 	 * have to be immediately recommitted.
 	 */
 	pminsize = PAGE_CEILING(minsize);
 	base_next_decommitted = (void *)((uintptr_t)base_pages + pminsize);
 	if (pminsize < csize)
 		pages_decommit(base_next_decommitted, csize - pminsize);
 #endif
 #ifdef MALLOC_STATS
 	base_mapped += csize;
 #endif
 
-	ret = false;
-RETURN:
-#ifdef MALLOC_PAGEFILE
-	if (pfd != -1)
-		pagefile_close(pfd);
-#endif
 	return (false);
 }
 
 static bool
 base_pages_alloc(size_t minsize)
 {
 
-	if (base_pages_alloc_mmap(minsize) == false)
-		return (false);
+#ifdef MALLOC_DSS
+	if (opt_dss) {
+		if (base_pages_alloc_dss(minsize) == false)
+			return (false);
+	}
+
+	if (opt_mmap && minsize != 0)
+#endif
+	{
+		if (base_pages_alloc_mmap(minsize) == false)
+			return (false);
+	}
 
 	return (true);
 }
 
 static void *
 base_alloc(size_t size)
 {
 	void *ret;
@@ -1836,48 +1833,16 @@ base_node_dealloc(extent_node_t *node)
 	malloc_mutex_lock(&base_mtx);
 	VALGRIND_FREELIKE_BLOCK(node, 0);
 	VALGRIND_MALLOCLIKE_BLOCK(node, sizeof(extent_node_t *), 0, false);
 	*(extent_node_t **)node = base_nodes;
 	base_nodes = node;
 	malloc_mutex_unlock(&base_mtx);
 }
 
-static reserve_reg_t *
-base_reserve_reg_alloc(void)
-{
-	reserve_reg_t *ret;
-
-	malloc_mutex_lock(&base_mtx);
-	if (base_reserve_regs != NULL) {
-		ret = base_reserve_regs;
-		base_reserve_regs = *(reserve_reg_t **)ret;
-		VALGRIND_FREELIKE_BLOCK(ret, 0);
-		VALGRIND_MALLOCLIKE_BLOCK(ret, sizeof(reserve_reg_t), 0, false);
-		malloc_mutex_unlock(&base_mtx);
-	} else {
-		malloc_mutex_unlock(&base_mtx);
-		ret = (reserve_reg_t *)base_alloc(sizeof(reserve_reg_t));
-	}
-
-	return (ret);
-}
-
-static void
-base_reserve_reg_dealloc(reserve_reg_t *reg)
-{
-
-	malloc_mutex_lock(&base_mtx);
-	VALGRIND_FREELIKE_BLOCK(reg, 0);
-	VALGRIND_MALLOCLIKE_BLOCK(reg, sizeof(reserve_reg_t *), 0, false);
-	*(reserve_reg_t **)reg = base_reserve_regs;
-	base_reserve_regs = reg;
-	malloc_mutex_unlock(&base_mtx);
-}
-
 /******************************************************************************/
 
 #ifdef MALLOC_STATS
 static void
 stats_print(arena_t *arena)
 {
 	unsigned i, gap_start;
 
@@ -2036,17 +2001,17 @@ rb_wrap(static, extent_tree_ad_, extent_
  */
 /******************************************************************************/
 /*
  * Begin chunk management functions.
  */
 
 #ifdef MOZ_MEMORY_WINDOWS
 static void *
-pages_map(void *addr, size_t size, int pfd)
+pages_map(void *addr, size_t size)
 {
 	void *ret;
 
 	ret = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE,
 	    PAGE_READWRITE);
 
 	return (ret);
 }
@@ -2059,17 +2024,17 @@ pages_unmap(void *addr, size_t size)
 		_malloc_message(_getprogname(),
 		    ": (malloc) Error in VirtualFree()\n", "", "");
 		if (opt_abort)
 			abort();
 	}
 }
 #elif (defined(MOZ_MEMORY_DARWIN))
 static void *
-pages_map(void *addr, size_t size, int pfd)
+pages_map(void *addr, size_t size)
 {
 	void *ret;
 	kern_return_t err;
 	int flags;
 
 	if (addr != NULL) {
 		ret = addr;
 		flags = 0;
@@ -2111,34 +2076,26 @@ pages_copy(void *dest, const void *src, 
 	assert(n >= VM_COPY_MIN);
 	assert((void *)((uintptr_t)src & ~pagesize_mask) == src);
 
 	vm_copy(mach_task_self(), (vm_address_t)src, (vm_size_t)n,
 	    (vm_address_t)dest);
 }
 #else /* MOZ_MEMORY_DARWIN */
 static void *
-pages_map(void *addr, size_t size, int pfd)
+pages_map(void *addr, size_t size)
 {
 	void *ret;
 
 	/*
 	 * We don't use MAP_FIXED here, because it can cause the *replacement*
 	 * of existing mappings, and we only want to create new mappings.
 	 */
-#ifdef MALLOC_PAGEFILE
-	if (pfd != -1) {
-		ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE |
-		    MAP_NOSYNC, pfd, 0);
-	} else
-#endif
-	       {
-		ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE |
-		    MAP_ANON, -1, 0);
-	}
+	ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
+	    -1, 0);
 	assert(ret != NULL);
 
 	if (ret == MAP_FAILED)
 		ret = NULL;
 	else if (addr != NULL && ret != addr) {
 		/*
 		 * We succeeded in mapping memory, but not in the right place.
 		 */
@@ -2170,380 +2127,427 @@ pages_unmap(void *addr, size_t size)
 		_malloc_message(_getprogname(),
 		    ": (malloc) Error in munmap(): ", buf, "\n");
 		if (opt_abort)
 			abort();
 	}
 }
 #endif
 
+#ifdef MALLOC_DSS
+static void *
+chunk_alloc_dss(size_t size)
+{
+
+	/*
+	 * sbrk() uses a signed increment argument, so take care not to
+	 * interpret a huge allocation request as a negative increment.
+	 */
+	if ((intptr_t)size < 0)
+		return (NULL);
+
+	malloc_mutex_lock(&dss_mtx);
+	if (dss_prev != (void *)-1) {
+		intptr_t incr;
+
+		/*
+		 * The loop is necessary to recover from races with other
+		 * threads that are using the DSS for something other than
+		 * malloc.
+		 */
+		do {
+			void *ret;
+
+			/* Get the current end of the DSS. */
+			dss_max = sbrk(0);
+
+			/*
+			 * Calculate how much padding is necessary to
+			 * chunk-align the end of the DSS.
+			 */
+			incr = (intptr_t)size
+			    - (intptr_t)CHUNK_ADDR2OFFSET(dss_max);
+			if (incr == (intptr_t)size)
+				ret = dss_max;
+			else {
+				ret = (void *)((intptr_t)dss_max + incr);
+				incr += size;
+			}
+
+			dss_prev = sbrk(incr);
+			if (dss_prev == dss_max) {
+				/* Success. */
+				dss_max = (void *)((intptr_t)dss_prev + incr);
+				malloc_mutex_unlock(&dss_mtx);
+				return (ret);
+			}
+		} while (dss_prev != (void *)-1);
+	}
+	malloc_mutex_unlock(&dss_mtx);
+
+	return (NULL);
+}
+
+static void *
+chunk_recycle_dss(size_t size, bool zero)
+{
+	extent_node_t *node, key;
+
+	key.addr = NULL;
+	key.size = size;
+	malloc_mutex_lock(&dss_mtx);
+	node = extent_tree_szad_nsearch(&dss_chunks_szad, &key);
+	if (node != NULL) {
+		void *ret = node->addr;
+
+		/* Remove node from the tree. */
+		extent_tree_szad_remove(&dss_chunks_szad, node);
+		if (node->size == size) {
+			extent_tree_ad_remove(&dss_chunks_ad, node);
+			base_node_dealloc(node);
+		} else {
+			/*
+			 * Insert the remainder of node's address range as a
+			 * smaller chunk.  Its position within dss_chunks_ad
+			 * does not change.
+			 */
+			assert(node->size > size);
+			node->addr = (void *)((uintptr_t)node->addr + size);
+			node->size -= size;
+			extent_tree_szad_insert(&dss_chunks_szad, node);
+		}
+		malloc_mutex_unlock(&dss_mtx);
+
+		if (zero)
+			memset(ret, 0, size);
+		return (ret);
+	}
+	malloc_mutex_unlock(&dss_mtx);
+
+	return (NULL);
+}
+#endif
+
+#ifdef MOZ_MEMORY_WINDOWS
 static inline void *
-chunk_alloc_mmap(size_t size, bool pagefile)
+chunk_alloc_mmap(size_t size)
 {
 	void *ret;
 	size_t offset;
-	int pfd;
-
-#ifdef MALLOC_PAGEFILE
-	if (opt_pagefile && pagefile) {
-		pfd = pagefile_init(size);
-		if (pfd == -1)
-			return (NULL);
-	} else
-#endif
-		pfd = -1;
 
 	/*
 	 * Windows requires that there be a 1:1 mapping between VM
 	 * allocation/deallocation operations.  Therefore, take care here to
 	 * acquire the final result via one mapping operation.  This means
 	 * unmapping any preliminary result that is not correctly aligned.
-	 *
-	 * The MALLOC_PAGEFILE code also benefits from this mapping algorithm,
-	 * since it reduces the number of page files.
 	 */
 
-	ret = pages_map(NULL, size, pfd);
+	ret = pages_map(NULL, size);
 	if (ret == NULL)
-		goto RETURN;
+		return (NULL);
 
 	offset = CHUNK_ADDR2OFFSET(ret);
 	if (offset != 0) {
 		/* Deallocate, then try to allocate at (ret + size - offset). */
 		pages_unmap(ret, size);
-		ret = pages_map((void *)((uintptr_t)ret + size - offset), size,
-		    pfd);
+		ret = pages_map((void *)((uintptr_t)ret + size - offset), size);
 		while (ret == NULL) {
 			/*
 			 * Over-allocate in order to map a memory region that
 			 * is definitely large enough.
 			 */
-			ret = pages_map(NULL, size + chunksize, -1);
+			ret = pages_map(NULL, size + chunksize);
 			if (ret == NULL)
-				goto RETURN;
+				return (NULL);
 			/*
 			 * Deallocate, then allocate the correct size, within
 			 * the over-sized mapping.
 			 */
 			offset = CHUNK_ADDR2OFFSET(ret);
 			pages_unmap(ret, size + chunksize);
 			if (offset == 0)
-				ret = pages_map(ret, size, pfd);
+				ret = pages_map(ret, size);
 			else {
 				ret = pages_map((void *)((uintptr_t)ret +
-				    chunksize - offset), size, pfd);
+				    chunksize - offset), size);
 			}
 			/*
 			 * Failure here indicates a race with another thread, so
 			 * try again.
 			 */
 		}
 	}
 
-RETURN:
-#ifdef MALLOC_PAGEFILE
-	if (pfd != -1)
-		pagefile_close(pfd);
-#endif
 	return (ret);
 }
-
-#ifdef MALLOC_PAGEFILE
-static int
-pagefile_init(size_t size)
+#else
+static inline void *
+chunk_alloc_mmap(size_t size)
 {
-	int ret;
-	size_t i;
-	char pagefile_path[PATH_MAX];
-	char zbuf[MALLOC_PAGEFILE_WRITE_SIZE];
+	void *ret;
+	size_t offset;
 
 	/*
-	 * Create a temporary file, then immediately unlink it so that it will
-	 * not persist.
+	 * Ideally, there would be a way to specify alignment to mmap() (like
+	 * NetBSD has), but in the absence of such a feature, we have to work
+	 * hard to efficiently create aligned mappings.  The reliable, but
+	 * expensive method is to create a mapping that is over-sized, then
+	 * trim the excess.  However, that always results in at least one call
+	 * to pages_unmap().
+	 *
+	 * A more optimistic approach is to try mapping precisely the right
+	 * amount, then try to append another mapping if alignment is off.  In
+	 * practice, this works out well as long as the application is not
+	 * interleaving mappings via direct mmap() calls.  If we do run into a
+	 * situation where there is an interleaved mapping and we are unable to
+	 * extend an unaligned mapping, our best option is to momentarily
+	 * revert to the reliable-but-expensive method.  This will tend to
+	 * leave a gap in the memory map that is too small to cause later
+	 * problems for the optimistic method.
 	 */
-	strcpy(pagefile_path, pagefile_templ);
-	ret = mkstemp(pagefile_path);
-	if (ret == -1)
-		return (ret);
-	if (unlink(pagefile_path)) {
-		char buf[STRERROR_BUF];
-
-		strerror_r(errno, buf, sizeof(buf));
-		_malloc_message(_getprogname(), ": (malloc) Error in unlink(\"",
-		    pagefile_path, "\"):");
-		_malloc_message(buf, "\n", "", "");
-		if (opt_abort)
-			abort();
-	}
-
-	/*
-	 * Write sequential zeroes to the file in order to assure that disk
-	 * space is committed, with minimal fragmentation.  It would be
-	 * sufficient to write one zero per disk block, but that potentially
-	 * results in more system calls, for no real gain.
-	 */
-	memset(zbuf, 0, sizeof(zbuf));
-	for (i = 0; i < size; i += sizeof(zbuf)) {
-		if (write(ret, zbuf, sizeof(zbuf)) != sizeof(zbuf)) {
-			if (errno != ENOSPC) {
-				char buf[STRERROR_BUF];
-
-				strerror_r(errno, buf, sizeof(buf));
-				_malloc_message(_getprogname(),
-				    ": (malloc) Error in write(): ", buf, "\n");
-				if (opt_abort)
-					abort();
+
+	ret = pages_map(NULL, size);
+	if (ret == NULL)
+		return (NULL);
+
+	offset = CHUNK_ADDR2OFFSET(ret);
+	if (offset != 0) {
+		/* Try to extend chunk boundary. */
+		if (pages_map((void *)((uintptr_t)ret + size),
+		    chunksize - offset) == NULL) {
+			/*
+			 * Extension failed.  Clean up, then revert to the
+			 * reliable-but-expensive method.
+			 */
+			pages_unmap(ret, size);
+
+			/* Beware size_t wrap-around. */
+			if (size + chunksize <= size)
+				return NULL;
+
+			ret = pages_map(NULL, size + chunksize);
+			if (ret == NULL)
+				return (NULL);
+
+			/* Clean up unneeded leading/trailing space. */
+			offset = CHUNK_ADDR2OFFSET(ret);
+			if (offset != 0) {
+				/* Leading space. */
+				pages_unmap(ret, chunksize - offset);
+
+				ret = (void *)((uintptr_t)ret +
+				    (chunksize - offset));
+
+				/* Trailing space. */
+				pages_unmap((void *)((uintptr_t)ret + size),
+				    offset);
+			} else {
+				/* Trailing space only. */
+				pages_unmap((void *)((uintptr_t)ret + size),
+				    chunksize);
 			}
-			pagefile_close(ret);
-			return (-1);
+		} else {
+			/* Clean up unneeded leading space. */
+			pages_unmap(ret, chunksize - offset);
+			ret = (void *)((uintptr_t)ret + (chunksize - offset));
 		}
 	}
 
 	return (ret);
 }
-
-static void
-pagefile_close(int pfd)
-{
-
-	if (close(pfd)) {
-		char buf[STRERROR_BUF];
-
-		strerror_r(errno, buf, sizeof(buf));
-		_malloc_message(_getprogname(),
-		    ": (malloc) Error in close(): ", buf, "\n");
-		if (opt_abort)
-			abort();
-	}
-}
 #endif
 
 static void *
-chunk_recycle_reserve(size_t size, bool zero)
-{
-	extent_node_t *node, key;
-
-#ifdef MALLOC_DECOMMIT
-	if (size != chunksize)
-		return (NULL);
-#endif
-
-	key.addr = NULL;
-	key.size = size;
-	malloc_mutex_lock(&reserve_mtx);
-	node = extent_tree_szad_nsearch(&reserve_chunks_szad, &key);
-	if (node != NULL) {
-		void *ret = node->addr;
-
-		/* Remove node from the tree. */
-		extent_tree_szad_remove(&reserve_chunks_szad, node);
-#ifndef MALLOC_DECOMMIT
-		if (node->size == size) {
-#else
-			assert(node->size == size);
-#endif
-			extent_tree_ad_remove(&reserve_chunks_ad, node);
-			base_node_dealloc(node);
-#ifndef MALLOC_DECOMMIT
-		} else {
-			/*
-			 * Insert the remainder of node's address range as a
-			 * smaller chunk.  Its position within reserve_chunks_ad
-			 * does not change.
-			 */
-			assert(node->size > size);
-			node->addr = (void *)((uintptr_t)node->addr + size);
-			node->size -= size;
-			extent_tree_szad_insert(&reserve_chunks_szad, node);
-		}
-#endif
-		reserve_cur -= size;
-		/*
-		 * Try to replenish the reserve if this allocation depleted it.
-		 */
-#ifndef MALLOC_DECOMMIT
-		if (reserve_cur < reserve_min) {
-			size_t diff = reserve_min - reserve_cur;
-#else
-		while (reserve_cur < reserve_min) {
-#  define diff chunksize
-#endif
-			void *chunk;
-
-			malloc_mutex_unlock(&reserve_mtx);
-			chunk = chunk_alloc_mmap(diff, true);
-			malloc_mutex_lock(&reserve_mtx);
-			if (chunk == NULL) {
-				uint64_t seq = 0;
-
-				do {
-					seq = reserve_notify(RESERVE_CND_LOW,
-					    size, seq);
-				} while (reserve_cur < reserve_min && seq != 0);
-			} else {
-				extent_node_t *node;
-
-				node = chunk_dealloc_reserve(chunk, diff);
-				if (node == NULL) {
-					uint64_t seq = 0;
-
-					pages_unmap(chunk, diff);
-					do {
-						seq = reserve_notify(
-						    RESERVE_CND_LOW, size, seq);
-					} while (reserve_cur < reserve_min &&
-					    seq != 0);
-				}
-			}
-		}
-		malloc_mutex_unlock(&reserve_mtx);
-
-#ifdef MALLOC_DECOMMIT
-		pages_commit(ret, size);
-#  undef diff
-#else
-		if (zero)
-			memset(ret, 0, size);
-#endif
-		return (ret);
-	}
-	malloc_mutex_unlock(&reserve_mtx);
-
-	return (NULL);
-}
-
-static void *
-chunk_alloc(size_t size, bool zero, bool pagefile)
+chunk_alloc(size_t size, bool zero)
 {
 	void *ret;
 
 	assert(size != 0);
 	assert((size & chunksize_mask) == 0);
 
-	ret = chunk_recycle_reserve(size, zero);
-	if (ret != NULL)
-		goto RETURN;
-
-	ret = chunk_alloc_mmap(size, pagefile);
-	if (ret != NULL) {
-#ifdef MALLOC_STATS
-		stats_chunks.nchunks += (size / chunksize);
-#endif
-		goto RETURN;
+#ifdef MALLOC_DSS
+	if (opt_dss) {
+		ret = chunk_recycle_dss(size, zero);
+		if (ret != NULL) {
+			goto RETURN;
+		}
+
+		ret = chunk_alloc_dss(size);
+		if (ret != NULL)
+			goto RETURN;
+	}
+
+	if (opt_mmap)
+#endif
+	{
+		ret = chunk_alloc_mmap(size);
+		if (ret != NULL)
+			goto RETURN;
 	}
 
 	/* All strategies for allocation failed. */
 	ret = NULL;
 RETURN:
 #ifdef MALLOC_STATS
-	if (ret != NULL)
+	if (ret != NULL) {
+		stats_chunks.nchunks += (size / chunksize);
 		stats_chunks.curchunks += (size / chunksize);
+	}
 	if (stats_chunks.curchunks > stats_chunks.highchunks)
 		stats_chunks.highchunks = stats_chunks.curchunks;
 #endif
 
 	assert(CHUNK_ADDR2BASE(ret) == ret);
 	return (ret);
 }
 
+#ifdef MALLOC_DSS
 static extent_node_t *
-chunk_dealloc_reserve(void *chunk, size_t size)
+chunk_dealloc_dss_record(void *chunk, size_t size)
 {
-	extent_node_t *node;
-
-#ifdef MALLOC_DECOMMIT
-	if (size != chunksize)
-		return (NULL);
-#else
-	extent_node_t *prev, key;
+	extent_node_t *node, *prev, key;
 
 	key.addr = (void *)((uintptr_t)chunk + size);
-	node = extent_tree_ad_nsearch(&reserve_chunks_ad, &key);
+	node = extent_tree_ad_nsearch(&dss_chunks_ad, &key);
 	/* Try to coalesce forward. */
 	if (node != NULL && node->addr == key.addr) {
 		/*
 		 * Coalesce chunk with the following address range.  This does
-		 * not change the position within reserve_chunks_ad, so only
-		 * remove/insert from/into reserve_chunks_szad.
+		 * not change the position within dss_chunks_ad, so only
+		 * remove/insert from/into dss_chunks_szad.
 		 */
-		extent_tree_szad_remove(&reserve_chunks_szad, node);
+		extent_tree_szad_remove(&dss_chunks_szad, node);
 		node->addr = chunk;
 		node->size += size;
-		extent_tree_szad_insert(&reserve_chunks_szad, node);
+		extent_tree_szad_insert(&dss_chunks_szad, node);
 	} else {
-#endif
-		/* Coalescing forward failed, so insert a new node. */
+		/*
+		 * Coalescing forward failed, so insert a new node.  Drop
+		 * dss_mtx during node allocation, since it is possible that a
+		 * new base chunk will be allocated.
+		 */
+		malloc_mutex_unlock(&dss_mtx);
 		node = base_node_alloc();
+		malloc_mutex_lock(&dss_mtx);
 		if (node == NULL)
 			return (NULL);
 		node->addr = chunk;
 		node->size = size;
-		extent_tree_ad_insert(&reserve_chunks_ad, node);
-		extent_tree_szad_insert(&reserve_chunks_szad, node);
-#ifndef MALLOC_DECOMMIT
+		extent_tree_ad_insert(&dss_chunks_ad, node);
+		extent_tree_szad_insert(&dss_chunks_szad, node);
 	}
 
 	/* Try to coalesce backward. */
-	prev = extent_tree_ad_prev(&reserve_chunks_ad, node);
+	prev = extent_tree_ad_prev(&dss_chunks_ad, node);
 	if (prev != NULL && (void *)((uintptr_t)prev->addr + prev->size) ==
 	    chunk) {
 		/*
 		 * Coalesce chunk with the previous address range.  This does
-		 * not change the position within reserve_chunks_ad, so only
-		 * remove/insert node from/into reserve_chunks_szad.
+		 * not change the position within dss_chunks_ad, so only
+		 * remove/insert node from/into dss_chunks_szad.
 		 */
-		extent_tree_szad_remove(&reserve_chunks_szad, prev);
-		extent_tree_ad_remove(&reserve_chunks_ad, prev);
-
-		extent_tree_szad_remove(&reserve_chunks_szad, node);
+		extent_tree_szad_remove(&dss_chunks_szad, prev);
+		extent_tree_ad_remove(&dss_chunks_ad, prev);
+
+		extent_tree_szad_remove(&dss_chunks_szad, node);
 		node->addr = prev->addr;
 		node->size += prev->size;
-		extent_tree_szad_insert(&reserve_chunks_szad, node);
+		extent_tree_szad_insert(&dss_chunks_szad, node);
 
 		base_node_dealloc(prev);
 	}
-#endif
-
-#ifdef MALLOC_DECOMMIT
-	pages_decommit(chunk, size);
-#else
-	madvise(chunk, size, MADV_FREE);
-#endif
-
-	reserve_cur += size;
-	if (reserve_cur > reserve_max)
-		reserve_shrink();
 
 	return (node);
 }
 
+static bool
+chunk_dealloc_dss(void *chunk, size_t size)
+{
+
+	malloc_mutex_lock(&dss_mtx);
+	if ((uintptr_t)chunk >= (uintptr_t)dss_base
+	    && (uintptr_t)chunk < (uintptr_t)dss_max) {
+		extent_node_t *node;
+
+		/* Try to coalesce with other unused chunks. */
+		node = chunk_dealloc_dss_record(chunk, size);
+		if (node != NULL) {
+			chunk = node->addr;
+			size = node->size;
+		}
+
+		/* Get the current end of the DSS. */
+		dss_max = sbrk(0);
+
+		/*
+		 * Try to shrink the DSS if this chunk is at the end of the
+		 * DSS.  The sbrk() call here is subject to a race condition
+		 * with threads that use brk(2) or sbrk(2) directly, but the
+		 * alternative would be to leak memory for the sake of poorly
+		 * designed multi-threaded programs.
+		 */
+		if ((void *)((uintptr_t)chunk + size) == dss_max
+		    && (dss_prev = sbrk(-(intptr_t)size)) == dss_max) {
+			/* Success. */
+			dss_max = (void *)((intptr_t)dss_prev - (intptr_t)size);
+
+			if (node != NULL) {
+				extent_tree_szad_remove(&dss_chunks_szad, node);
+				extent_tree_ad_remove(&dss_chunks_ad, node);
+				base_node_dealloc(node);
+			}
+			malloc_mutex_unlock(&dss_mtx);
+		} else {
+			malloc_mutex_unlock(&dss_mtx);
+#ifdef MOZ_MEMORY_WINDOWS
+			VirtualAlloc(chunk, size, MEM_RESET, PAGE_READWRITE);
+#elif (defined(MOZ_MEMORY_DARWIN))
+			mmap(chunk, size, PROT_READ | PROT_WRITE, MAP_PRIVATE
+			    | MAP_ANON | MAP_FIXED, -1, 0);
+#else
+			madvise(chunk, size, MADV_FREE);
+#endif
+		}
+
+		return (false);
+	}
+	malloc_mutex_unlock(&dss_mtx);
+
+	return (true);
+}
+#endif
+
 static void
 chunk_dealloc_mmap(void *chunk, size_t size)
 {
 
 	pages_unmap(chunk, size);
 }
 
 static void
 chunk_dealloc(void *chunk, size_t size)
 {
-	extent_node_t *node;
 
 	assert(chunk != NULL);
 	assert(CHUNK_ADDR2BASE(chunk) == chunk);
 	assert(size != 0);
 	assert((size & chunksize_mask) == 0);
 
 #ifdef MALLOC_STATS
 	stats_chunks.curchunks -= (size / chunksize);
 #endif
 
-	/* Try to merge chunk into the reserve. */
-	node = chunk_dealloc_reserve(chunk, size);
-	if (node == NULL)
+#ifdef MALLOC_DSS
+	if (opt_dss) {
+		if (chunk_dealloc_dss(chunk, size) == false)
+			return;
+	}
+
+	if (opt_mmap)
+#endif
 		chunk_dealloc_mmap(chunk, size);
 }
 
 /*
  * End chunk management functions.
  */
 /******************************************************************************/
 /*
@@ -2952,17 +2956,17 @@ arena_run_split(arena_t *arena, arena_ru
 				    CHUNK_MAP_DECOMMITTED;
 			}
 
 			pages_commit((void *)((uintptr_t)chunk + ((run_ind + i)
 			    << pagesize_2pow)), (j << pagesize_2pow));
 #  ifdef MALLOC_STATS
 			arena->stats.ncommit++;
 #  endif
-		} else /* No need to zero since commit zeros. */
+		}
 #endif
 
 		/* Zero if necessary. */
 		if (zero) {
 			if ((chunk->map[run_ind + i] & CHUNK_MAP_UNTOUCHED)
 			    == 0) {
 				VALGRIND_MALLOCLIKE_BLOCK((void *)((uintptr_t)
 				    chunk + ((run_ind + i) << pagesize_2pow)),
@@ -3005,183 +3009,163 @@ arena_run_split(arena_t *arena, arena_ru
 		arena_chunk_node_dealloc(chunk, nodeB);
 	}
 
 	chunk->pages_used += need_pages;
 	if (chunk->ndirty == 0 && old_ndirty > 0)
 		arena_chunk_tree_dirty_remove(&arena->chunks_dirty, chunk);
 }
 
-static void
-arena_chunk_init(arena_t *arena, arena_chunk_t *chunk)
+static arena_chunk_t *
+arena_chunk_alloc(arena_t *arena)
 {
+	arena_chunk_t *chunk;
 	extent_node_t *node;
 
-	VALGRIND_MALLOCLIKE_BLOCK(chunk, (arena_chunk_header_npages <<
-	    pagesize_2pow), 0, false);
+	if (arena->spare != NULL) {
+		chunk = arena->spare;
+		arena->spare = NULL;
+	} else {
+		chunk = (arena_chunk_t *)chunk_alloc(chunksize, true);
+		if (chunk == NULL)
+			return (NULL);
+		VALGRIND_MALLOCLIKE_BLOCK(chunk, (arena_chunk_header_npages <<
+		    pagesize_2pow), 0, false);
 #ifdef MALLOC_STATS
-	arena->stats.mapped += chunksize;
-#endif
-
-	chunk->arena = arena;
-
-	arena_chunk_tree_all_insert(&arena->chunks_all, chunk);
-
-	/*
-	 * Claim that no pages are in use, since the header is merely overhead.
-	 */
-	chunk->pages_used = 0;
-	chunk->ndirty = 0;
-
-	/* Initialize the map to contain one maximal free untouched run. */
-	memset(chunk->map, (CHUNK_MAP_LARGE | CHUNK_MAP_POS_MASK),
-	    arena_chunk_header_npages);
-	memset(&chunk->map[arena_chunk_header_npages], (CHUNK_MAP_UNTOUCHED
+		arena->stats.mapped += chunksize;
+#endif
+
+		chunk->arena = arena;
+
+		arena_chunk_tree_all_insert(&arena->chunks_all, chunk);
+
+		/*
+		 * Claim that no pages are in use, since the header is merely
+		 * overhead.
+		 */
+		chunk->pages_used = 0;
+		chunk->ndirty = 0;
+
+		/*
+		 * Initialize the map to contain one maximal free untouched
+		 * run.
+		 */
+		memset(chunk->map, (CHUNK_MAP_LARGE | CHUNK_MAP_POS_MASK),
+		    arena_chunk_header_npages);
+		memset(&chunk->map[arena_chunk_header_npages],
+		    (CHUNK_MAP_UNTOUCHED
 #ifdef MALLOC_DECOMMIT
-	    | CHUNK_MAP_DECOMMITTED
-#endif
-	    ), (chunk_npages -
-	    arena_chunk_header_npages));
-
-	/* Initialize the tree of unused extent nodes. */
-	extent_tree_ad_new(&chunk->nodes);
-	chunk->nodes_past = (extent_node_t *)QUANTUM_CEILING(
-	    (uintptr_t)&chunk->map[chunk_npages]);
+		    | CHUNK_MAP_DECOMMITTED
+#endif
+		    ), (chunk_npages -
+		    arena_chunk_header_npages));
+
+		/* Initialize the tree of unused extent nodes. */
+		extent_tree_ad_new(&chunk->nodes);
+		chunk->nodes_past = (extent_node_t *)QUANTUM_CEILING(
+		    (uintptr_t)&chunk->map[chunk_npages]);
 
 #ifdef MALLOC_DECOMMIT
-	/*
-	 * Start out decommitted, in order to force a closer correspondence
-	 * between dirty pages and committed untouched pages.
-	 */
-	pages_decommit((void *)((uintptr_t)chunk +
-	    (arena_chunk_header_npages << pagesize_2pow)),
-	    ((chunk_npages - arena_chunk_header_npages) <<
-	    pagesize_2pow));
+		/*
+		 * Start out decommitted, in order to force a closer
+		 * correspondence between dirty pages and committed untouched
+		 * pages.
+		 */
+		pages_decommit((void *)((uintptr_t)chunk +
+		    (arena_chunk_header_npages << pagesize_2pow)),
+		    ((chunk_npages - arena_chunk_header_npages) <<
+		    pagesize_2pow));
 #  ifdef MALLOC_STATS
-	arena->stats.ndecommit++;
-	arena->stats.decommitted += (chunk_npages - arena_chunk_header_npages);
+		arena->stats.ndecommit++;
+		arena->stats.decommitted += (chunk_npages -
+		    arena_chunk_header_npages);
 #  endif
 #endif
+	}
 
 	/* Insert the run into the runs_avail_* red-black trees. */
 	node = arena_chunk_node_alloc(chunk);
 	node->addr = (void *)((uintptr_t)chunk + (arena_chunk_header_npages <<
 	    pagesize_2pow));
 	node->size = chunksize - (arena_chunk_header_npages << pagesize_2pow);
 	extent_tree_szad_insert(&arena->runs_avail_szad, node);
 	extent_tree_ad_insert(&arena->runs_avail_ad, node);
+
+	return (chunk);
 }
 
 static void
 arena_chunk_dealloc(arena_t *arena, arena_chunk_t *chunk)
 {
 	extent_node_t *node, key;
 
+	if (arena->spare != NULL) {
+		arena_chunk_tree_all_remove(&chunk->arena->chunks_all,
+		    arena->spare);
+		if (arena->spare->ndirty > 0) {
+			arena_chunk_tree_dirty_remove(
+			    &chunk->arena->chunks_dirty, arena->spare);
+			arena->ndirty -= arena->spare->ndirty;
+		}
+		VALGRIND_FREELIKE_BLOCK(arena->spare, 0);
+		chunk_dealloc((void *)arena->spare, chunksize);
+#ifdef MALLOC_STATS
+		arena->stats.mapped -= chunksize;
+#endif
+	}
+
 	/*
 	 * Remove run from the runs trees, regardless of whether this chunk
 	 * will be cached, so that the arena does not use it.  Dirty page
 	 * flushing only uses the chunks_dirty tree, so leaving this chunk in
 	 * the chunks_* trees is sufficient for that purpose.
 	 */
 	key.addr = (void *)((uintptr_t)chunk + (arena_chunk_header_npages <<
 	    pagesize_2pow));
 	node = extent_tree_ad_search(&arena->runs_avail_ad, &key);
 	assert(node != NULL);
 	extent_tree_szad_remove(&arena->runs_avail_szad, node);
 	extent_tree_ad_remove(&arena->runs_avail_ad, node);
 	arena_chunk_node_dealloc(chunk, node);
 
-	arena_chunk_tree_all_remove(&chunk->arena->chunks_all,
-	    chunk);
-	if (chunk->ndirty > 0) {
-		arena_chunk_tree_dirty_remove(
-		    &chunk->arena->chunks_dirty, chunk);
-		arena->ndirty -= chunk->ndirty;
-	}
-	VALGRIND_FREELIKE_BLOCK(chunk, 0);
-	chunk_dealloc((void *)chunk, chunksize);
-#ifdef MALLOC_STATS
-	arena->stats.mapped -= chunksize;
-#endif
+	arena->spare = chunk;
 }
 
 static arena_run_t *
 arena_run_alloc(arena_t *arena, size_t size, bool small, bool zero)
 {
 	arena_chunk_t *chunk;
 	arena_run_t *run;
 	extent_node_t *node, key;
 
 	assert(size <= (chunksize - (arena_chunk_header_npages <<
 	    pagesize_2pow)));
 	assert((size & pagesize_mask) == 0);
 
-	chunk = NULL;
-	while (true) {
-		/* Search the arena's chunks for the lowest best fit. */
-		key.addr = NULL;
-		key.size = size;
-		node = extent_tree_szad_nsearch(&arena->runs_avail_szad, &key);
-		if (node != NULL) {
-			if (chunk != NULL)
-				chunk_dealloc(chunk, chunksize);
-			run = (arena_run_t *)node->addr;
-			arena_run_split(arena, run, size, small, zero);
-			return (run);
-		}
-
-		/*
-		 * No usable runs.  Create a new chunk from which to allocate
-		 * the run.
-		 */
-		if (chunk == NULL) {
-			uint64_t chunk_seq;
-
-			/*
-			 * Record the chunk allocation sequence number in order
-			 * to detect races.
-			 */
-			arena->chunk_seq++;
-			chunk_seq = arena->chunk_seq;
-
-			/*
-			 * Drop the arena lock while allocating a chunk, since
-			 * reserve notifications may cause recursive
-			 * allocation.  Dropping the lock here opens an
-			 * allocataion race, but we recover.
-			 */
-			malloc_mutex_unlock(&arena->lock);
-			chunk = (arena_chunk_t *)chunk_alloc(chunksize, true,
-			    true);
-			malloc_mutex_lock(&arena->lock);
-
-			/*
-			 * If this thread raced with another such that multiple
-			 * chunks were allocated, make sure that there is still
-			 * inadequate space before using this chunk.
-			 */
-			if (chunk_seq != arena->chunk_seq)
-				continue;
-
-			/*
-			 * Check for an error *after* checking for a race,
-			 * since a race could also cause a transient OOM
-			 * condition.
-			 */
-			if (chunk == NULL)
-				return (NULL);
-		}
-
-		arena_chunk_init(arena, chunk);
-		run = (arena_run_t *)((uintptr_t)chunk +
-		    (arena_chunk_header_npages << pagesize_2pow));
-		/* Update page map. */
+	/* Search the arena's chunks for the lowest best fit. */
+	key.addr = NULL;
+	key.size = size;
+	node = extent_tree_szad_nsearch(&arena->runs_avail_szad, &key);
+	if (node != NULL) {
+		run = (arena_run_t *)node->addr;
 		arena_run_split(arena, run, size, small, zero);
 		return (run);
 	}
+
+	/*
+	 * No usable runs.  Create a new chunk from which to allocate the run.
+	 */
+	chunk = arena_chunk_alloc(arena);
+	if (chunk == NULL)
+		return (NULL);
+	run = (arena_run_t *)((uintptr_t)chunk + (arena_chunk_header_npages <<
+	    pagesize_2pow));
+	/* Update page map. */
+	arena_run_split(arena, run, size, small, zero);
+	return (run);
 }
 
 static void
 arena_purge(arena_t *arena)
 {
 	arena_chunk_t *chunk;
 	size_t i, npages;
 #ifdef MALLOC_DEBUG
@@ -4507,21 +4491,20 @@ arena_new(arena_t *arena)
 
 	if (malloc_spin_init(&arena->lock))
 		return (true);
 
 #ifdef MALLOC_STATS
 	memset(&arena->stats, 0, sizeof(arena_stats_t));
 #endif
 
-	arena->chunk_seq = 0;
-
 	/* Initialize chunks. */
 	arena_chunk_tree_all_new(&arena->chunks_all);
 	arena_chunk_tree_dirty_new(&arena->chunks_dirty);
+	arena->spare = NULL;
 
 	arena->ndirty = 0;
 
 	extent_tree_szad_new(&arena->runs_avail_szad);
 	extent_tree_ad_new(&arena->runs_avail_ad);
 	extent_tree_ad_new(&arena->runs_alloced_ad);
 
 #ifdef MALLOC_BALANCE
@@ -4639,17 +4622,17 @@ huge_malloc(size_t size, bool zero)
 		return (NULL);
 	}
 
 	/* Allocate an extent node with which to track the chunk. */
 	node = base_node_alloc();
 	if (node == NULL)
 		return (NULL);
 
-	ret = chunk_alloc(csize, zero, true);
+	ret = chunk_alloc(csize, zero);
 	if (ret == NULL) {
 		base_node_dealloc(node);
 		return (NULL);
 	}
 
 	/* Insert node into huge. */
 	node->addr = ret;
 #ifdef MALLOC_DECOMMIT
@@ -4707,17 +4690,16 @@ static void *
 huge_palloc(size_t alignment, size_t size)
 {
 	void *ret;
 	size_t alloc_size, chunk_size, offset;
 #ifdef MALLOC_DECOMMIT
 	size_t psize;
 #endif
 	extent_node_t *node;
-	int pfd;
 
 	/*
 	 * This allocation requires alignment that is even larger than chunk
 	 * alignment.  This means that huge_malloc() isn't good enough.
 	 *
 	 * Allocate almost twice as many chunks as are demanded by the size or
 	 * alignment, in order to assure the alignment can be achieved, then
 	 * unmap leading and trailing chunks.
@@ -4731,53 +4713,73 @@ huge_palloc(size_t alignment, size_t siz
 	else
 		alloc_size = (alignment << 1) - chunksize;
 
 	/* Allocate an extent node with which to track the chunk. */
 	node = base_node_alloc();
 	if (node == NULL)
 		return (NULL);
 
+#ifdef MOZ_MEMORY_WINDOWS
 	/*
 	 * Windows requires that there be a 1:1 mapping between VM
 	 * allocation/deallocation operations.  Therefore, take care here to
 	 * acquire the final result via one mapping operation.
-	 *
-	 * The MALLOC_PAGEFILE code also benefits from this mapping algorithm,
-	 * since it reduces the number of page files.
 	 */
-#ifdef MALLOC_PAGEFILE
-	if (opt_pagefile) {
-		pfd = pagefile_init(size);
-		if (pfd == -1)
-			return (NULL);
-	} else
-#endif
-		pfd = -1;
 	do {
 		void *over;
 
-		over = chunk_alloc(alloc_size, false, false);
+		over = chunk_alloc(alloc_size, false);
 		if (over == NULL) {
 			base_node_dealloc(node);
-			ret = NULL;
-			goto RETURN;
+			return (NULL);
 		}
 
 		offset = (uintptr_t)over & (alignment - 1);
 		assert((offset & chunksize_mask) == 0);
 		assert(offset < alloc_size);
 		ret = (void *)((uintptr_t)over + offset);
 		chunk_dealloc(over, alloc_size);
-		ret = pages_map(ret, chunk_size, pfd);
+		ret = pages_map(ret, chunk_size);
 		/*
 		 * Failure here indicates a race with another thread, so try
 		 * again.
 		 */
 	} while (ret == NULL);
+#else
+	ret = chunk_alloc(alloc_size, false);
+	if (ret == NULL) {
+		base_node_dealloc(node);
+		return (NULL);
+	}
+
+	offset = (uintptr_t)ret & (alignment - 1);
+	assert((offset & chunksize_mask) == 0);
+	assert(offset < alloc_size);
+	if (offset == 0) {
+		/* Trim trailing space. */
+		chunk_dealloc((void *)((uintptr_t)ret + chunk_size), alloc_size
+		    - chunk_size);
+	} else {
+		size_t trailsize;
+
+		/* Trim leading space. */
+		chunk_dealloc(ret, alignment - offset);
+
+		ret = (void *)((uintptr_t)ret + (alignment - offset));
+
+		trailsize = alloc_size - (alignment - offset) - chunk_size;
+		if (trailsize != 0) {
+		    /* Trim trailing space. */
+		    assert(trailsize < alloc_size);
+		    chunk_dealloc((void *)((uintptr_t)ret + chunk_size),
+			trailsize);
+		}
+	}
+#endif
 
 	/* Insert node into huge. */
 	node->addr = ret;
 #ifdef MALLOC_DECOMMIT
 	psize = PAGE_CEILING(size);
 	node->size = psize;
 #else
 	node->size = chunk_size;
@@ -4818,21 +4820,16 @@ huge_palloc(size_t alignment, size_t siz
 	else if (opt_zero)
 #  ifdef MALLOC_DECOMMIT
 		memset(ret, 0, psize);
 #  else
 		memset(ret, 0, chunk_size);
 #  endif
 #endif
 
-RETURN:
-#ifdef MALLOC_PAGEFILE
-	if (pfd != -1)
-		pagefile_close(pfd);
-#endif
 	return (ret);
 }
 
 static void *
 huge_ralloc(void *ptr, size_t size, size_t oldsize)
 {
 	void *ret;
 	size_t copysize;
@@ -4933,20 +4930,22 @@ huge_dalloc(void *ptr)
 #ifdef MALLOC_STATS
 	huge_ndalloc++;
 	huge_allocated -= node->size;
 #endif
 
 	malloc_mutex_unlock(&huge_mtx);
 
 	/* Unmap chunk. */
+#ifdef MALLOC_DSS
 #ifdef MALLOC_FILL
-	if (opt_junk)
+	if (opt_dss && opt_junk)
 		memset(node->addr, 0x5a, node->size);
 #endif
+#endif
 #ifdef MALLOC_DECOMMIT
 	chunk_dealloc(node->addr, CHUNK_CEILING(node->size));
 #else
 	chunk_dealloc(node->addr, node->size);
 #endif
 	VALGRIND_FREELIKE_BLOCK(node->addr, 0);
 
 	base_node_dealloc(node);
@@ -5070,21 +5069,24 @@ malloc_print_stats(void)
 #ifdef NDEBUG
 		    "disabled",
 #else
 		    "enabled",
 #endif
 		    "\n", "");
 		_malloc_message("Boolean MALLOC_OPTIONS: ",
 		    opt_abort ? "A" : "a", "", "");
+#ifdef MALLOC_DSS
+		_malloc_message(opt_dss ? "D" : "d", "", "", "");
+#endif
 #ifdef MALLOC_FILL
 		_malloc_message(opt_junk ? "J" : "j", "", "", "");
 #endif
-#ifdef MALLOC_PAGEFILE
-		_malloc_message(opt_pagefile ? "o" : "O", "", "", "");
+#ifdef MALLOC_DSS
+		_malloc_message(opt_mmap ? "M" : "m", "", "", "");
 #endif
 		_malloc_message("P", "", "", "");
 #ifdef MALLOC_UTRACE
 		_malloc_message(opt_utrace ? "U" : "u", "", "", "");
 #endif
 #ifdef MALLOC_SYSV
 		_malloc_message(opt_sysv ? "V" : "v", "", "", "");
 #endif
@@ -5152,32 +5154,16 @@ malloc_print_stats(void)
 #ifdef MOZ_MEMORY_WINDOWS
 			malloc_printf("Allocated: %lu, mapped: %lu\n",
 			    allocated, mapped);
 #else
 			malloc_printf("Allocated: %zu, mapped: %zu\n",
 			    allocated, mapped);
 #endif
 
-			malloc_mutex_lock(&reserve_mtx);
-			malloc_printf("Reserve:    min          "
-			    "cur          max\n");
-#ifdef MOZ_MEMORY_WINDOWS
-			malloc_printf("   %12lu %12lu %12lu\n",
-			    CHUNK_CEILING(reserve_min) >> opt_chunk_2pow,
-			    reserve_cur >> opt_chunk_2pow,
-			    reserve_max >> opt_chunk_2pow);
-#else
-			malloc_printf("   %12zu %12zu %12zu\n",
-			    CHUNK_CEILING(reserve_min) >> opt_chunk_2pow,
-			    reserve_cur >> opt_chunk_2pow,
-			    reserve_max >> opt_chunk_2pow);
-#endif
-			malloc_mutex_unlock(&reserve_mtx);
-
 #ifdef MALLOC_BALANCE
 			malloc_printf("Arena balance reassignments: %llu\n",
 			    nbalance);
 #endif
 
 			/* Print chunk stats. */
 			{
 				chunk_stats_t chunks_stats;
@@ -5298,47 +5284,16 @@ malloc_init_hard(void)
 	/*
 	 * We assume that pagesize is a power of 2 when calculating
 	 * pagesize_mask and pagesize_2pow.
 	 */
 	assert(((result - 1) & result) == 0);
 	pagesize_mask = result - 1;
 	pagesize_2pow = ffs((int)result) - 1;
 
-#ifdef MALLOC_PAGEFILE
-	/*
-	 * Determine where to create page files.  It is insufficient to
-	 * unconditionally use P_tmpdir (typically "/tmp"), since for some
-	 * operating systems /tmp is a separate filesystem that is rather small.
-	 * Therefore prefer, in order, the following locations:
-	 *
-	 * 1) MALLOC_TMPDIR
-	 * 2) TMPDIR
-	 * 3) P_tmpdir
-	 */
-	{
-		char *s;
-		size_t slen;
-		static const char suffix[] = "/jemalloc.XXXXXX";
-
-		if ((s = getenv("MALLOC_TMPDIR")) == NULL && (s =
-		    getenv("TMPDIR")) == NULL)
-			s = P_tmpdir;
-		slen = strlen(s);
-		if (slen + sizeof(suffix) > sizeof(pagefile_templ)) {
-			_malloc_message(_getprogname(),
-			    ": (malloc) Page file path too long\n",
-			    "", "");
-			abort();
-		}
-		memcpy(pagefile_templ, s, slen);
-		memcpy(&pagefile_templ[slen], suffix, sizeof(suffix));
-	}
-#endif
-
 	for (i = 0; i < 3; i++) {
 		unsigned j;
 
 		/* Get runtime configuration. */
 		switch (i) {
 		case 0:
 #ifndef MOZ_MEMORY_WINDOWS
 			if ((linklen = readlink("/etc/malloc.conf", buf,
@@ -5429,31 +5384,35 @@ MALLOC_OUT:
 #ifdef MALLOC_BALANCE
 					if (opt_balance_threshold == 0)
 						opt_balance_threshold = 1;
 					else if ((opt_balance_threshold << 1)
 					    > opt_balance_threshold)
 						opt_balance_threshold <<= 1;
 #endif
 					break;
+				case 'd':
+#ifdef MALLOC_DSS
+					opt_dss = false;
+#endif
+					break;
+				case 'D':
+#ifdef MALLOC_DSS
+					opt_dss = true;
+#endif
+					break;
 				case 'f':
 					opt_dirty_max >>= 1;
 					break;
 				case 'F':
 					if (opt_dirty_max == 0)
 						opt_dirty_max = 1;
 					else if ((opt_dirty_max << 1) != 0)
 						opt_dirty_max <<= 1;
 					break;
-				case 'g':
-					opt_reserve_range_lshift--;
-					break;
-				case 'G':
-					opt_reserve_range_lshift++;
-					break;
 #ifdef MALLOC_FILL
 				case 'j':
 					opt_junk = false;
 					break;
 				case 'J':
 					opt_junk = true;
 					break;
 #endif
@@ -5466,53 +5425,47 @@ MALLOC_OUT:
 					if (opt_chunk_2pow > pagesize_2pow + 1)
 						opt_chunk_2pow--;
 					break;
 				case 'K':
 					if (opt_chunk_2pow + 1 <
 					    (sizeof(size_t) << 3))
 						opt_chunk_2pow++;
 					break;
+				case 'm':
+#ifdef MALLOC_DSS
+					opt_mmap = false;
+#endif
+					break;
+				case 'M':
+#ifdef MALLOC_DSS
+					opt_mmap = true;
+#endif
+					break;
 				case 'n':
 					opt_narenas_lshift--;
 					break;
 				case 'N':
 					opt_narenas_lshift++;
 					break;
-#ifdef MALLOC_PAGEFILE
-				case 'o':
-					/* Do not over-commit. */
-					opt_pagefile = true;
-					break;
-				case 'O':
-					/* Allow over-commit. */
-					opt_pagefile = false;
-					break;
-#endif
 				case 'p':
 					opt_print_stats = false;
 					break;
 				case 'P':
 					opt_print_stats = true;
 					break;
 				case 'q':
 					if (opt_quantum_2pow > QUANTUM_2POW_MIN)
 						opt_quantum_2pow--;
 					break;
 				case 'Q':
 					if (opt_quantum_2pow < pagesize_2pow -
 					    1)
 						opt_quantum_2pow++;
 					break;
-				case 'r':
-					opt_reserve_min_lshift--;
-					break;
-				case 'R':
-					opt_reserve_min_lshift++;
-					break;
 				case 's':
 					if (opt_small_max_2pow >
 					    QUANTUM_2POW_MIN)
 						opt_small_max_2pow--;
 					break;
 				case 'S':
 					if (opt_small_max_2pow < pagesize_2pow
 					    - 1)
@@ -5560,16 +5513,22 @@ MALLOC_OUT:
 					    "in malloc options: '", cbuf,
 					    "'\n");
 				}
 				}
 			}
 		}
 	}
 
+#ifdef MALLOC_DSS
+	/* Make sure that there is some method for acquiring memory. */
+	if (opt_dss == false && opt_mmap == false)
+		opt_mmap = true;
+#endif
+
 	/* Take care to call atexit() only once. */
 	if (opt_print_stats) {
 #ifndef MOZ_MEMORY_WINDOWS
 		/* Print statistics at exit. */
 		atexit(malloc_print_stats);
 #endif
 	}
 
@@ -5628,28 +5587,44 @@ MALLOC_OUT:
 	assert(quantum >= sizeof(void *));
 	assert(quantum <= pagesize);
 	assert(chunksize >= pagesize);
 	assert(quantum * 4 <= chunksize);
 
 	/* Initialize chunks data. */
 	malloc_mutex_init(&huge_mtx);
 	extent_tree_ad_new(&huge);
+#ifdef MALLOC_DSS
+	malloc_mutex_init(&dss_mtx);
+	dss_base = sbrk(0);
+	dss_prev = dss_base;
+	dss_max = dss_base;
+	extent_tree_szad_new(&dss_chunks_szad);
+	extent_tree_ad_new(&dss_chunks_ad);
+#endif
 #ifdef MALLOC_STATS
 	huge_nmalloc = 0;
 	huge_ndalloc = 0;
 	huge_allocated = 0;
 #endif
 
 	/* Initialize base allocation data structures. */
 #ifdef MALLOC_STATS
 	base_mapped = 0;
 #endif
+#ifdef MALLOC_DSS
+	/*
+	 * Allocate a base chunk here, since it doesn't actually have to be
+	 * chunk-aligned.  Doing this before allocating any other chunks allows
+	 * the use of space that would otherwise be wasted.
+	 */
+	if (opt_dss)
+		base_pages_alloc(0);
+#endif
 	base_nodes = NULL;
-	base_reserve_regs = NULL;
 	malloc_mutex_init(&base_mtx);
 
 #ifdef MOZ_MEMORY_NARENAS_DEFAULT_ONE
 	narenas = 1;
 #else
 	if (ncpus > 1) {
 		/*
 		 * For SMP systems, create four times as many arenas as there
@@ -5760,37 +5735,16 @@ MALLOC_OUT:
 	 * called for other threads.  The seed value doesn't really matter.
 	 */
 #ifdef MALLOC_BALANCE
 	SPRN(balance, 42);
 #endif
 
 	malloc_spin_init(&arenas_lock);
 
-	/*
-	 * Configure and initialize the memory reserve.  This needs to happen
-	 * late during initialization, since chunks are allocated.
-	 */
-	malloc_mutex_init(&reserve_mtx);
-	reserve_min = 0;
-	reserve_cur = 0;
-	reserve_max = chunksize * narenas;
-	if (RESERVE_RANGE_2POW_DEFAULT + opt_reserve_range_lshift >= 0) {
-		reserve_max += chunksize << (RESERVE_RANGE_2POW_DEFAULT +
-		    opt_reserve_range_lshift);
-	}
-	ql_new(&reserve_regs);
-	reserve_seq = 0;
-	extent_tree_szad_new(&reserve_chunks_szad);
-	extent_tree_ad_new(&reserve_chunks_ad);
-	if (RESERVE_MIN_2POW_DEFAULT + opt_reserve_min_lshift >= 0) {
-		reserve_min_set(chunksize << (RESERVE_MIN_2POW_DEFAULT +
-		    opt_reserve_min_lshift));
-	}
-
 	malloc_initialized = true;
 #ifndef MOZ_MEMORY_WINDOWS
 	malloc_mutex_unlock(&init_lock);
 #endif
 	return (false);
 }
 
 /* XXX Why not just expose malloc_print_stats()? */
@@ -6112,21 +6066,31 @@ jemalloc_stats(jemalloc_stats_t *stats)
 	size_t i;
 
 	assert(stats != NULL);
 
 	/*
 	 * Gather runtime settings.
 	 */
 	stats->opt_abort = opt_abort;
+	stats->opt_dss =
+#ifdef MALLOC_DSS
+	    opt_dss ? true :
+#endif
+	    false;
 	stats->opt_junk =
 #ifdef MALLOC_FILL
 	    opt_junk ? true :
 #endif
 	    false;
+	stats->opt_mmap =
+#ifdef MALLOC_DSS
+	    opt_mmap == false ? false :
+#endif
+	    true;
 	stats->opt_utrace =
 #ifdef MALLOC_UTRACE
 	    opt_utrace ? true :
 #endif
 	    false;
 	stats->opt_sysv =
 #ifdef MALLOC_SYSV
 	    opt_sysv ? true :
@@ -6151,22 +6115,16 @@ jemalloc_stats(jemalloc_stats_t *stats)
 #endif
 	    ;
 	stats->quantum = quantum;
 	stats->small_max = small_max;
 	stats->large_max = arena_maxclass;
 	stats->chunksize = chunksize;
 	stats->dirty_max = opt_dirty_max;
 
-	malloc_mutex_lock(&reserve_mtx);
-	stats->reserve_min = reserve_min;
-	stats->reserve_max = reserve_max;
-	stats->reserve_cur = reserve_cur;
-	malloc_mutex_unlock(&reserve_mtx);
-
 	/*
 	 * Gather current memory usage statistics.
 	 */
 	stats->mapped = 0;
 	stats->committed = 0;
 	stats->allocated = 0;
 	stats->dirty = 0;
 
@@ -6214,457 +6172,16 @@ jemalloc_stats(jemalloc_stats_t *stats)
 		}
 	}
 
 #ifndef MALLOC_DECOMMIT
 	stats->committed = stats->mapped;
 #endif
 }
 
-void *
-xmalloc(size_t size)
-{
-	void *ret;
-
-	if (malloc_init())
-		reserve_fail(size, "xmalloc");
-
-	if (size == 0) {
-#ifdef MALLOC_SYSV
-		if (opt_sysv == false)
-#endif
-			size = 1;
-#ifdef MALLOC_SYSV
-		else {
-			_malloc_message(_getprogname(),
-			    ": (malloc) Error in xmalloc(): ",
-			    "invalid size 0", "\n");
-			abort();
-		}
-#endif
-	}
-
-	ret = imalloc(size);
-	if (ret == NULL) {
-		uint64_t seq = 0;
-
-		do {
-			seq = reserve_crit(size, "xmalloc", seq);
-			ret = imalloc(size);
-		} while (ret == NULL);
-	}
-
-	UTRACE(0, size, ret);
-	return (ret);
-}
-
-void *
-xcalloc(size_t num, size_t size)
-{
-	void *ret;
-	size_t num_size;
-
-	num_size = num * size;
-	if (malloc_init())
-		reserve_fail(num_size, "xcalloc");
-
-	if (num_size == 0) {
-#ifdef MALLOC_SYSV
-		if ((opt_sysv == false) && ((num == 0) || (size == 0)))
-#endif
-			num_size = 1;
-#ifdef MALLOC_SYSV
-		else {
-			_malloc_message(_getprogname(),
-			    ": (malloc) Error in xcalloc(): ",
-			    "invalid size 0", "\n");
-			abort();
-		}
-#endif
-	/*
-	 * Try to avoid division here.  We know that it isn't possible to
-	 * overflow during multiplication if neither operand uses any of the
-	 * most significant half of the bits in a size_t.
-	 */
-	} else if (((num | size) & (SIZE_T_MAX << (sizeof(size_t) << 2)))
-	    && (num_size / size != num)) {
-		/* size_t overflow. */
-		_malloc_message(_getprogname(),
-		    ": (malloc) Error in xcalloc(): ",
-		    "size overflow", "\n");
-		abort();
-	}
-
-	ret = icalloc(num_size);
-	if (ret == NULL) {
-		uint64_t seq = 0;
-
-		do {
-			seq = reserve_crit(num_size, "xcalloc", seq);
-			ret = icalloc(num_size);
-		} while (ret == NULL);
-	}
-
-	UTRACE(0, num_size, ret);
-	return (ret);
-}
-
-void *
-xrealloc(void *ptr, size_t size)
-{
-	void *ret;
-
-	if (size == 0) {
-#ifdef MALLOC_SYSV
-		if (opt_sysv == false)
-#endif
-			size = 1;
-#ifdef MALLOC_SYSV
-		else {
-			if (ptr != NULL)
-				idalloc(ptr);
-			_malloc_message(_getprogname(),
-			    ": (malloc) Error in xrealloc(): ",
-			    "invalid size 0", "\n");
-			abort();
-		}
-#endif
-	}
-
-	if (ptr != NULL) {
-		assert(malloc_initialized);
-
-		ret = iralloc(ptr, size);
-		if (ret == NULL) {
-			uint64_t seq = 0;
-
-			do {
-				seq = reserve_crit(size, "xrealloc", seq);
-				ret = iralloc(ptr, size);
-			} while (ret == NULL);
-		}
-	} else {
-		if (malloc_init())
-			reserve_fail(size, "xrealloc");
-
-		ret = imalloc(size);
-		if (ret == NULL) {
-			uint64_t seq = 0;
-
-			do {
-				seq = reserve_crit(size, "xrealloc", seq);
-				ret = imalloc(size);
-			} while (ret == NULL);
-		}
-	}
-
-	UTRACE(ptr, size, ret);
-	return (ret);
-}
-
-void *
-xmemalign(size_t alignment, size_t size)
-{
-	void *ret;
-
-	assert(((alignment - 1) & alignment) == 0 && alignment >=
-	    sizeof(void *));
-
-	if (malloc_init())
-		reserve_fail(size, "xmemalign");
-
-	ret = ipalloc(alignment, size);
-	if (ret == NULL) {
-		uint64_t seq = 0;
-
-		do {
-			seq = reserve_crit(size, "xmemalign", seq);
-			ret = ipalloc(alignment, size);
-		} while (ret == NULL);
-	}
-
-	UTRACE(0, size, ret);
-	return (ret);
-}
-
-static void
-reserve_shrink(void)
-{
-	extent_node_t *node;
-
-	assert(reserve_cur > reserve_max);
-#ifdef MALLOC_DEBUG
-	{
-		extent_node_t *node;
-		size_t reserve_size;
-
-		reserve_size = 0;
-		rb_foreach_begin(extent_node_t, link_szad, &reserve_chunks_szad,
-		    node) {
-			reserve_size += node->size;
-		} rb_foreach_end(extent_node_t, link_szad, &reserve_chunks_szad,
-		    node)
-		assert(reserve_size == reserve_cur);
-
-		reserve_size = 0;
-		rb_foreach_begin(extent_node_t, link_ad, &reserve_chunks_ad,
-		    node) {
-			reserve_size += node->size;
-		} rb_foreach_end(extent_node_t, link_ad, &reserve_chunks_ad,
-		    node)
-		assert(reserve_size == reserve_cur);
-	}
-#endif
-
-	/* Discard chunks until the the reserve is below the size limit. */
-	rb_foreach_reverse_begin(extent_node_t, link_ad, &reserve_chunks_ad,
-	    node) {
-#ifndef MALLOC_DECOMMIT
-		if (node->size <= reserve_cur - reserve_max) {
-#endif
-			extent_node_t *tnode = extent_tree_ad_prev(
-			    &reserve_chunks_ad, node);
-
-#ifdef MALLOC_DECOMMIT
-			assert(node->size <= reserve_cur - reserve_max);
-#endif
-
-			/* Discard the entire [multi-]chunk. */
-			extent_tree_szad_remove(&reserve_chunks_szad, node);
-			extent_tree_ad_remove(&reserve_chunks_ad, node);
-			reserve_cur -= node->size;
-			pages_unmap(node->addr, node->size);
-			base_node_dealloc(node);
-			if (reserve_cur == reserve_max)
-				break;
-
-			rb_foreach_reverse_prev(extent_node_t, link_ad,
-			    extent_ad_comp, &reserve_chunks_ad, tnode);
-#ifndef MALLOC_DECOMMIT
-		} else {
-			/* Discard the end of the multi-chunk. */
-			extent_tree_szad_remove(&reserve_chunks_szad, node);
-			node->size -= reserve_cur - reserve_max;
-			extent_tree_szad_insert(&reserve_chunks_szad, node);
-			pages_unmap((void *)((uintptr_t)node->addr +
-			    node->size), reserve_cur - reserve_max);
-			reserve_cur = reserve_max;
-			break;
-		}
-#endif
-		assert(reserve_cur > reserve_max);
-	} rb_foreach_reverse_end(extent_node_t, link_ad, &reserve_chunks_ad,
-	    node)
-}
-
-/* Send a condition notification. */
-static uint64_t
-reserve_notify(reserve_cnd_t cnd, size_t size, uint64_t seq)
-{
-	reserve_reg_t *reg;
-
-	/* seq is used to keep track of distinct condition-causing events. */
-	if (seq == 0) {
-		/* Allocate new sequence number. */
-		reserve_seq++;
-		seq = reserve_seq;
-	}
-
-	/*
-	 * Advance to the next callback registration and send a notification,
-	 * unless one has already been sent for this condition-causing event.
-	 */
-	reg = ql_first(&reserve_regs);
-	if (reg == NULL)
-		return (0);
-	ql_first(&reserve_regs) = ql_next(&reserve_regs, reg, link);
-	if (reg->seq == seq)
-		return (0);
-	reg->seq = seq;
-	malloc_mutex_unlock(&reserve_mtx);
-	reg->cb(reg->ctx, cnd, size);
-	malloc_mutex_lock(&reserve_mtx);
-
-	return (seq);
-}
-
-/* Allocation failure due to OOM.  Try to free some memory via callbacks. */
-static uint64_t
-reserve_crit(size_t size, const char *fname, uint64_t seq)
-{
-
-	/*
-	 * Send one condition notification.  Iteration is handled by the
-	 * caller of this function.
-	 */
-	malloc_mutex_lock(&reserve_mtx);
-	seq = reserve_notify(RESERVE_CND_CRIT, size, seq);
-	malloc_mutex_unlock(&reserve_mtx);
-
-	/* If no notification could be sent, then no further recourse exists. */
-	if (seq == 0)
-		reserve_fail(size, fname);
-
-	return (seq);
-}
-
-/* Permanent allocation failure due to OOM. */
-static void
-reserve_fail(size_t size, const char *fname)
-{
-	uint64_t seq = 0;
-
-	/* Send fail notifications. */
-	malloc_mutex_lock(&reserve_mtx);
-	do {
-		seq = reserve_notify(RESERVE_CND_FAIL, size, seq);
-	} while (seq != 0);
-	malloc_mutex_unlock(&reserve_mtx);
-
-	/* Terminate the application. */
-	_malloc_message(_getprogname(),
-	    ": (malloc) Error in ", fname, "(): out of memory\n");
-	abort();
-}
-
-bool
-reserve_cb_register(reserve_cb_t *cb, void *ctx)
-{
-	reserve_reg_t *reg = base_reserve_reg_alloc();
-	if (reg == NULL)
-		return (true);
-
-	ql_elm_new(reg, link);
-	reg->cb = cb;
-	reg->ctx = ctx;
-	reg->seq = 0;
-
-	malloc_mutex_lock(&reserve_mtx);
-	ql_head_insert(&reserve_regs, reg, link);
-	malloc_mutex_unlock(&reserve_mtx);
-
-	return (false);
-}
-
-bool
-reserve_cb_unregister(reserve_cb_t *cb, void *ctx)
-{
-	reserve_reg_t *reg = NULL;
-
-	malloc_mutex_lock(&reserve_mtx);
-	ql_foreach(reg, &reserve_regs, link) {
-		if (reg->cb == cb && reg->ctx == ctx) {
-			ql_remove(&reserve_regs, reg, link);
-			break;
-		}
-	}
-	malloc_mutex_unlock(&reserve_mtx);
-
-	if (reg != NULL)
-		base_reserve_reg_dealloc(reg);
-		return (false);
-	return (true);
-}
-
-size_t
-reserve_cur_get(void)
-{
-	size_t ret;
-
-	malloc_mutex_lock(&reserve_mtx);
-	ret = reserve_cur;
-	malloc_mutex_unlock(&reserve_mtx);
-
-	return (ret);
-}
-
-size_t
-reserve_min_get(void)
-{
-	size_t ret;
-
-	malloc_mutex_lock(&reserve_mtx);
-	ret = reserve_min;
-	malloc_mutex_unlock(&reserve_mtx);
-
-	return (ret);
-}
-
-bool
-reserve_min_set(size_t min)
-{
-
-	min = CHUNK_CEILING(min);
-
-	malloc_mutex_lock(&reserve_mtx);
-	/* Keep |reserve_max - reserve_min| the same. */
-	if (min < reserve_min) {
-		reserve_max -= reserve_min - min;
-		reserve_min = min;
-	} else {
-		/* Protect against wrap-around. */
-		if (reserve_max + min - reserve_min < reserve_max) {
-			reserve_min = SIZE_T_MAX - (reserve_max - reserve_min)
-			    - chunksize + 1;
-			reserve_max = SIZE_T_MAX - chunksize + 1;
-		} else {
-			reserve_max += min - reserve_min;
-			reserve_min = min;
-		}
-	}
-
-	/* Resize the reserve if necessary. */
-	if (reserve_cur < reserve_min) {
-		size_t size = reserve_min - reserve_cur;
-
-		/* Force the reserve to grow by allocating/deallocating. */
-		malloc_mutex_unlock(&reserve_mtx);
-#ifdef MALLOC_DECOMMIT
-		{
-			void **chunks;
-			size_t i, n;
-
-			n = size >> opt_chunk_2pow;
-			chunks = imalloc(n * sizeof(void *));
-			if (chunks == NULL)
-				return (true);
-			for (i = 0; i < n; i++) {
-				chunks[i] = huge_malloc(chunksize, false);
-				if (chunks[i] == NULL) {
-					size_t j;
-
-					for (j = 0; j < i; j++) {
-						huge_dalloc(chunks[j]);
-					}
-					idalloc(chunks);
-					return (true);
-				}
-			}
-			for (i = 0; i < n; i++)
-				huge_dalloc(chunks[i]);
-			idalloc(chunks);
-		}
-#else
-		{
-			void *x = huge_malloc(size, false);
-			if (x == NULL) {
-				return (true);
-			}
-			huge_dalloc(x);
-		}
-#endif
-	} else if (reserve_cur > reserve_max) {
-		reserve_shrink();
-		malloc_mutex_unlock(&reserve_mtx);
-	} else
-		malloc_mutex_unlock(&reserve_mtx);
-
-	return (false);
-}
-
 #ifdef MOZ_MEMORY_WINDOWS
 void*
 _recalloc(void *ptr, size_t count, size_t size)
 {
 	size_t oldsize = (ptr != NULL) ? isalloc(ptr) : 0;
 	size_t newsize = count * size;
 
 	/*
@@ -6700,16 +6217,17 @@ void*
 size_t
 _msize(const void *ptr)
 {
 
 	return malloc_usable_size(ptr);
 }
 #endif
 
+
 /*
  * End non-standard functions.
  */
 /******************************************************************************/
 /*
  * Begin library-private functions, used by threading libraries for protection
  * of malloc during fork().  These functions are only called if the program is
  * running in threaded mode, so there is no need to check whether the program
@@ -6728,25 +6246,33 @@ void
 		if (arenas[i] != NULL)
 			malloc_spin_lock(&arenas[i]->lock);
 	}
 	malloc_spin_unlock(&arenas_lock);
 
 	malloc_mutex_lock(&base_mtx);
 
 	malloc_mutex_lock(&huge_mtx);
+
+#ifdef MALLOC_DSS
+	malloc_mutex_lock(&dss_mtx);
+#endif
 }
 
 void
 _malloc_postfork(void)
 {
 	unsigned i;
 
 	/* Release all mutexes, now that fork() has completed. */
 
+#ifdef MALLOC_DSS
+	malloc_mutex_unlock(&dss_mtx);
+#endif
+
 	malloc_mutex_unlock(&huge_mtx);
 
 	malloc_mutex_unlock(&base_mtx);
 
 	malloc_spin_lock(&arenas_lock);
 	for (i = 0; i < narenas; i++) {
 		if (arenas[i] != NULL)
 			malloc_spin_unlock(&arenas[i]->lock);
--- a/memory/jemalloc/jemalloc.h
+++ b/memory/jemalloc/jemalloc.h
@@ -14,164 +14,44 @@ extern const char	*_malloc_options;
  * sure that the compiled results of jemalloc.c are in sync with this header
  * file.
  */
 typedef struct {
 	/*
 	 * Run-time configuration settings.
 	 */
 	bool	opt_abort;	/* abort(3) on error? */
+	bool	opt_dss;	/* Use sbrk(2) to map memory? */
 	bool	opt_junk;	/* Fill allocated/free memory with 0xa5/0x5a? */
+	bool	opt_mmap;	/* Use mmap(2) to map memory? */
 	bool	opt_utrace;	/* Trace all allocation events? */
 	bool	opt_sysv;	/* SysV semantics? */
 	bool	opt_xmalloc;	/* abort(3) on OOM? */
 	bool	opt_zero;	/* Fill allocated memory with 0x0? */
 	size_t	narenas;	/* Number of arenas. */
 	size_t	balance_threshold; /* Arena contention rebalance threshold. */
 	size_t	quantum;	/* Allocation quantum. */
 	size_t	small_max;	/* Max quantum-spaced allocation size. */
 	size_t	large_max;	/* Max sub-chunksize allocation size. */
 	size_t	chunksize;	/* Size of each virtual memory mapping. */
 	size_t	dirty_max;	/* Max dirty pages per arena. */
-	size_t	reserve_min;	/* reserve_low callback threshold. */
-	size_t	reserve_max;	/* Maximum reserve size before unmapping. */
 
 	/*
 	 * Current memory usage statistics.
 	 */
 	size_t	mapped;		/* Bytes mapped (not necessarily committed). */
 	size_t	committed;	/* Bytes committed (readable/writable). */
 	size_t	allocated;	/* Bytes allocted (in use by application). */
 	size_t	dirty;		/* Bytes dirty (committed unused pages). */
-	size_t	reserve_cur;	/* Current memory reserve. */
 } jemalloc_stats_t;
 
 #ifndef MOZ_MEMORY_DARWIN
 void	*malloc(size_t size);
 void	*valloc(size_t size);
 void	*calloc(size_t num, size_t size);
 void	*realloc(void *ptr, size_t size);
 void	free(void *ptr);
 #endif
 
 int	posix_memalign(void **memptr, size_t alignment, size_t size);
 void	*memalign(size_t alignment, size_t size);
 size_t	malloc_usable_size(const void *ptr);
 void	jemalloc_stats(jemalloc_stats_t *stats);
-
-/* The x*() functions never return NULL. */
-void	*xmalloc(size_t size);
-void	*xcalloc(size_t num, size_t size);
-void	*xrealloc(void *ptr, size_t size);
-void	*xmemalign(size_t alignment, size_t size);
-
-/*
- * The allocator maintains a memory reserve that is used to satisfy allocation
- * requests when no additional memory can be acquired from the operating
- * system.  Under normal operating conditions, the reserve size is at least
- * reserve_min bytes.  If the reserve is depleted or insufficient to satisfy an
- * allocation request, then condition notifications are sent to one or more of
- * the registered callback functions:
- *
- *   RESERVE_CND_LOW: The reserve had to be used to satisfy an allocation
- *                    request, which dropped the reserve size below the
- *                    minimum.  The callee should try to free memory in order
- *                    to restore the reserve.
- *
- *   RESERVE_CND_CRIT: The reserve was not large enough to satisfy a pending
- *                     allocation request.  Some callee must free adequate
- *                     memory in order to prevent application failure (unless
- *                     the condition spontaneously desists due to concurrent
- *                     deallocation).
- *
- *   RESERVE_CND_FAIL: An allocation request could not be satisfied, despite all
- *                     attempts.  The allocator is about to terminate the
- *                     application.
- *
- * The order in which the callback functions are called is only loosely
- * specified: in the absence of interposing callback
- * registrations/unregistrations, enabled callbacks will be called in an
- * arbitrary round-robin order.
- *
- * Condition notifications are sent to callbacks only while conditions exist.
- * For example, just before the allocator sends a RESERVE_CND_LOW condition
- * notification to a callback, the reserve is in fact depleted.  However, due
- * to allocator concurrency, the reserve may have been restored by the time the
- * callback function executes.  Furthermore, if the reserve is restored at some
- * point during the delivery of condition notifications to callbacks, no
- * further deliveries will occur, since the condition no longer exists.
- *
- * Callback functions can freely call back into the allocator (i.e. the
- * allocator releases all internal resources before calling each callback
- * function), though allocation is discouraged, since recursive callbacks are
- * likely to result, which places extra burden on the application to avoid
- * deadlock.
- *
- * Callback functions must be thread-safe, since it is possible that multiple
- * threads will call into the same callback function concurrently.
- */
-
-/* Memory reserve condition types. */
-typedef enum {
-	RESERVE_CND_LOW,
-	RESERVE_CND_CRIT,
-	RESERVE_CND_FAIL
-} reserve_cnd_t;
-
-/*
- * Reserve condition notification callback function type definition.
- *
- * Inputs:
- *   ctx: Opaque application data, as passed to reserve_cb_register().
- *   cnd: Condition type being delivered.
- *   size: Allocation request size for the allocation that caused the condition.
- */
-typedef void reserve_cb_t(void *ctx, reserve_cnd_t cnd, size_t size);
-
-/*
- * Register a callback function.
- *
- * Inputs:
- *   cb: Callback function pointer.
- *   ctx: Opaque application data, passed to cb().
- *
- * Output:
- *   ret: If true, failure due to OOM; success otherwise.
- */
-bool	reserve_cb_register(reserve_cb_t *cb, void *ctx);
-
-/*
- * Unregister a callback function.
- *
- * Inputs:
- *   cb: Callback function pointer.
- *   ctx: Opaque application data, same as that passed to reserve_cb_register().
- *
- * Output:
- *   ret: False upon success, true if the {cb,ctx} registration could not be
- *        found.
- */
-bool	reserve_cb_unregister(reserve_cb_t *cb, void *ctx);
-
-/*
- * Get the current reserve size.
- *
- * ret: Current reserve size.
- */
-size_t	reserve_cur_get(void);
-
-/*
- * Get the minimum acceptable reserve size.  If the reserve drops below this
- * value, the RESERVE_CND_LOW condition notification is sent to the callbacks.
- *
- * ret: Minimum acceptable reserve size.
- */
-size_t	reserve_min_get(void);
-
-/*
- * Set the minimum acceptable reserve size.
- *
- * min: Reserve threshold.  This value may be internally rounded up.
- * ret: False if the reserve was successfully resized; true otherwise.  Note
- *      that failure to resize the reserve also results in a RESERVE_CND_LOW
- *      condition.
- */
-bool	reserve_min_set(size_t min);