Bug 1420353 - Change how replace-malloc initializes, part 1. r=njn
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 22 Nov 2017 17:24:29 +0900
changeset 393626 ff3c38687a7968e40291118983b135daf067fa35
parent 393625 152f561ce602d17e2efd390e6d7afc30748fe49a
child 393627 4c6c6291935aa7a8739262b0941f7254ec14d8fd
push id32968
push usernerli@mozilla.com
push dateSat, 25 Nov 2017 10:04:31 +0000
treeherdermozilla-central@890ae45c3d15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1420353
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1420353 - Change how replace-malloc initializes, part 1. r=njn The allocator API is a moving target, and every time we change it, the surface for replace-malloc libraries grows. This causes some build system problems, because of the tricks in replace_malloc.mk, which require the full list of symbols. Considering the above and the goal of moving some of the replace-malloc libraries into mozglue, it becomes simpler to reduce the replace-malloc exposure to the initialization functions. So instead of the allocator poking into replace-malloc libraries for all the functions, we expect their replace_init function to alter the table of allocator functions it's passed to register its own functions. This means replace-malloc implementations now need to copy the original table, which is not a bad thing, as it allows function calls with one level of indirection less. It also replace_init functions to not actually register the replace-malloc functions in some cases, which will be useful when linking some replace-malloc libraries into mozglue. Note this is binary compatible with previously built replace-malloc libraries, but because those libraries wouldn't update the function table, they would stay disabled.
memory/build/malloc_decls.h
memory/build/mozjemalloc.cpp
memory/build/replace_malloc.h
memory/replace/dmd/DMD.cpp
memory/replace/logalloc/LogAlloc.cpp
mozglue/build/replace_malloc.mk
--- a/memory/build/malloc_decls.h
+++ b/memory/build/malloc_decls.h
@@ -33,17 +33,17 @@
 
 #ifndef MALLOC_FUNCS
 #define MALLOC_FUNCS                                                           \
   (MALLOC_FUNCS_MALLOC | MALLOC_FUNCS_JEMALLOC | MALLOC_FUNCS_ARENA)
 #endif
 
 #ifdef MALLOC_DECL
 #if MALLOC_FUNCS & MALLOC_FUNCS_INIT
-MALLOC_DECL(init, void, const malloc_table_t*)
+MALLOC_DECL(init, void, malloc_table_t*)
 #endif
 #if MALLOC_FUNCS & MALLOC_FUNCS_BRIDGE
 MALLOC_DECL(get_bridge, struct ReplaceMallocBridge*)
 #endif
 #if MALLOC_FUNCS & MALLOC_FUNCS_MALLOC_BASE
 MALLOC_DECL(malloc, void*, size_t)
 MALLOC_DECL(calloc, void*, size_t, size_t)
 MALLOC_DECL(realloc, void*, void*, size_t)
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -4817,22 +4817,20 @@ static
 #elif defined(__GNUC__)
 #define MOZ_REPLACE_WEAK __attribute__((weak))
 #endif
 
 #include "replace_malloc.h"
 
 #define MALLOC_DECL(name, return_type, ...) MozJemalloc::name,
 
-static const malloc_table_t malloc_table = {
+static malloc_table_t gReplaceMallocTable = {
 #include "malloc_decls.h"
 };
 
-static malloc_table_t replace_malloc_table;
-
 #ifdef MOZ_NO_REPLACE_FUNC_DECL
 #define MALLOC_DECL(name, return_type, ...)                                    \
   typedef return_type(name##_impl_t)(__VA_ARGS__);                             \
   name##_impl_t* replace_##name = nullptr;
 #define MALLOC_FUNCS (MALLOC_FUNCS_INIT | MALLOC_FUNCS_BRIDGE)
 #include "malloc_decls.h"
 #endif
 
@@ -4867,120 +4865,102 @@ replace_malloc_handle()
     return dlopen(replace_malloc_lib, RTLD_LAZY);
   }
   return nullptr;
 }
 
 #define REPLACE_MALLOC_GET_FUNC(handle, name)                                  \
   (name##_impl_t*)dlsym(handle, "replace_" #name)
 
-#else
-
-typedef bool replace_malloc_handle_t;
-
-static replace_malloc_handle_t
-replace_malloc_handle()
-{
-  return true;
-}
-
-#define REPLACE_MALLOC_GET_FUNC(handle, name) replace_##name
-
 #endif
 
 static void
 replace_malloc_init_funcs();
 
 // Below is the malloc implementation overriding jemalloc and calling the
 // replacement functions if they exist.
-static int replace_malloc_initialized = 0;
+static bool gReplaceMallocInitialized = false;
 static void
 init()
 {
-  replace_malloc_init_funcs();
+#ifdef MOZ_NO_REPLACE_FUNC_DECL
+  replace_malloc_handle_t handle = replace_malloc_handle();
+  if (handle) {
+#define MALLOC_DECL(name, ...)                                                 \
+  replace_##name = REPLACE_MALLOC_GET_FUNC(handle, name);
+
+#define MALLOC_FUNCS (MALLOC_FUNCS_INIT | MALLOC_FUNCS_BRIDGE)
+#include "malloc_decls.h"
+  }
+#endif
+
   // Set this *before* calling replace_init, otherwise if replace_init calls
   // malloc() we'll get an infinite loop.
-  replace_malloc_initialized = 1;
+  gReplaceMallocInitialized = true;
   if (replace_init) {
-    replace_init(&malloc_table);
-  }
+    replace_init(&gReplaceMallocTable);
+  }
+  replace_malloc_init_funcs();
 }
 
 #define MALLOC_DECL(name, return_type, ...)                                    \
   template<>                                                                   \
   inline return_type ReplaceMalloc::name(                                      \
     ARGS_HELPER(TYPED_ARGS, ##__VA_ARGS__))                                    \
   {                                                                            \
-    if (MOZ_UNLIKELY(!replace_malloc_initialized)) {                           \
+    if (MOZ_UNLIKELY(!gReplaceMallocInitialized)) {                            \
       init();                                                                  \
     }                                                                          \
-    return replace_malloc_table.name(ARGS_HELPER(ARGS, ##__VA_ARGS__));        \
+    return gReplaceMallocTable.name(ARGS_HELPER(ARGS, ##__VA_ARGS__));         \
   }
 #include "malloc_decls.h"
 
 MOZ_JEMALLOC_API struct ReplaceMallocBridge*
 get_bridge(void)
 {
-  if (MOZ_UNLIKELY(!replace_malloc_initialized)) {
+  if (MOZ_UNLIKELY(!gReplaceMallocInitialized)) {
     init();
   }
   if (MOZ_LIKELY(!replace_get_bridge)) {
     return nullptr;
   }
   return replace_get_bridge();
 }
 
 // posix_memalign, aligned_alloc, memalign and valloc all implement some kind
 // of aligned memory allocation. For convenience, a replace-malloc library can
 // skip defining replace_posix_memalign, replace_aligned_alloc and
 // replace_valloc, and default implementations will be automatically derived
 // from replace_memalign.
 static void
 replace_malloc_init_funcs()
 {
-  replace_malloc_handle_t handle = replace_malloc_handle();
-  if (handle) {
-#ifdef MOZ_NO_REPLACE_FUNC_DECL
-#define MALLOC_DECL(name, ...)                                                 \
-  replace_##name = REPLACE_MALLOC_GET_FUNC(handle, name);
-
-#define MALLOC_FUNCS (MALLOC_FUNCS_INIT | MALLOC_FUNCS_BRIDGE)
-#include "malloc_decls.h"
-#endif
-
+  if (gReplaceMallocTable.posix_memalign == MozJemalloc::posix_memalign &&
+      gReplaceMallocTable.memalign != MozJemalloc::memalign) {
+    gReplaceMallocTable.posix_memalign =
+      AlignedAllocator<ReplaceMalloc::memalign>::posix_memalign;
+  }
+  if (gReplaceMallocTable.aligned_alloc == MozJemalloc::aligned_alloc &&
+      gReplaceMallocTable.memalign != MozJemalloc::memalign) {
+    gReplaceMallocTable.aligned_alloc =
+      AlignedAllocator<ReplaceMalloc::memalign>::aligned_alloc;
+  }
+  if (gReplaceMallocTable.valloc == MozJemalloc::valloc &&
+      gReplaceMallocTable.memalign != MozJemalloc::memalign) {
+    gReplaceMallocTable.valloc =
+      AlignedAllocator<ReplaceMalloc::memalign>::valloc;
+  }
+  if (gReplaceMallocTable.moz_create_arena_with_params ==
+        MozJemalloc::moz_create_arena_with_params &&
+      gReplaceMallocTable.malloc != MozJemalloc::malloc) {
 #define MALLOC_DECL(name, ...)                                                 \
-  replace_malloc_table.name = REPLACE_MALLOC_GET_FUNC(handle, name);
-#include "malloc_decls.h"
-  }
-
-  if (!replace_malloc_table.posix_memalign && replace_malloc_table.memalign) {
-    replace_malloc_table.posix_memalign =
-      AlignedAllocator<ReplaceMalloc::memalign>::posix_memalign;
-  }
-  if (!replace_malloc_table.aligned_alloc && replace_malloc_table.memalign) {
-    replace_malloc_table.aligned_alloc =
-      AlignedAllocator<ReplaceMalloc::memalign>::aligned_alloc;
-  }
-  if (!replace_malloc_table.valloc && replace_malloc_table.memalign) {
-    replace_malloc_table.valloc =
-      AlignedAllocator<ReplaceMalloc::memalign>::valloc;
-  }
-  if (!replace_malloc_table.moz_create_arena_with_params &&
-      replace_malloc_table.malloc) {
-#define MALLOC_DECL(name, ...)                                                 \
-  replace_malloc_table.name = DummyArenaAllocator<ReplaceMalloc>::name;
+  gReplaceMallocTable.name = DummyArenaAllocator<ReplaceMalloc>::name;
 #define MALLOC_FUNCS MALLOC_FUNCS_ARENA
 #include "malloc_decls.h"
   }
-
-#define MALLOC_DECL(name, ...)                                                 \
-  if (!replace_malloc_table.name) {                                            \
-    replace_malloc_table.name = MozJemalloc::name;                             \
-  }
-#include "malloc_decls.h"
 }
 
 #endif // MOZ_REPLACE_MALLOC
   // ***************************************************************************
   // Definition of all the _impl functions
 
 #define GENERIC_MALLOC_DECL2(name, name_impl, return_type, ...)                \
   return_type name_impl(ARGS_HELPER(TYPED_ARGS, ##__VA_ARGS__))                \
--- a/memory/build/replace_malloc.h
+++ b/memory/build/replace_malloc.h
@@ -14,37 +14,39 @@
 // environment variables to the library path:
 //   - LD_PRELOAD on Linux,
 //   - DYLD_INSERT_LIBRARIES on OSX,
 //   - MOZ_REPLACE_MALLOC_LIB on Windows and Android.
 //
 // An initialization function is called before any malloc replacement
 // function, and has the following declaration:
 //
-//   void replace_init(const malloc_table_t *)
+//   void replace_init(malloc_table_t *)
 //
-// The const malloc_table_t pointer given to that function is a table
-// containing pointers to the original jemalloc implementation, so that
-// replacement functions can call them back if they need to. The pointer
-// itself can safely be kept around (no need to copy the table itself).
+// The malloc_table_t pointer given to that function is a table containing
+// pointers to the original allocator implementation, so that replacement
+// functions can call them back if they need to. The initialization function
+// needs to alter that table to replace the function it wants to replace.
+// If it needs the original implementation, it thus needs a copy of the
+// original table.
 //
 // The functions to be implemented in the external library are of the form:
 //
 //   void *replace_malloc(size_t size)
 //   {
 //     // Fiddle with the size if necessary.
 //     // orig->malloc doesn't have to be called if the external library
 //     // provides its own allocator, but in this case it will have to
 //     // implement all functions.
 //     void *ptr = orig->malloc(size);
 //     // Do whatever you want with the ptr.
 //     return ptr;
 //   }
 //
-// where "orig" is the pointer obtained from replace_init.
+// where "orig" is a pointer to a copy of the table replace_init got.
 //
 // See malloc_decls.h for a list of functions that can be replaced this
 // way. The implementations are all in the form:
 //   return_type replace_name(arguments [,...])
 //
 // They don't all need to be provided.
 //
 // Building a replace-malloc library is like rocket science. It can end up
@@ -78,19 +80,25 @@ MOZ_BEGIN_EXTERN_C
 // replace_malloc.c. Normally including this header will add function
 // definitions.
 #ifndef MOZ_NO_REPLACE_FUNC_DECL
 
 #ifndef MOZ_REPLACE_WEAK
 #define MOZ_REPLACE_WEAK
 #endif
 
+// Export replace_init and replace_get_bridge.
 #define MALLOC_DECL(name, return_type, ...)                                    \
   MOZ_EXPORT return_type replace_##name(__VA_ARGS__) MOZ_REPLACE_WEAK;
 
-#define MALLOC_FUNCS MALLOC_FUNCS_ALL
+#define MALLOC_FUNCS (MALLOC_FUNCS_INIT | MALLOC_FUNCS_BRIDGE)
+#include "malloc_decls.h"
+
+// Define the remaining replace_* functions as not exported.
+#define MALLOC_DECL(name, return_type, ...)                                    \
+  return_type replace_##name(__VA_ARGS__);
 #include "malloc_decls.h"
 
 #endif // MOZ_NO_REPLACE_FUNC_DECL
 
 MOZ_END_EXTERN_C
 
 #endif // replace_malloc_h
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -84,17 +84,17 @@ StatusMsg(const char* aFmt, ...)
 //---------------------------------------------------------------------------
 
 #ifndef DISALLOW_COPY_AND_ASSIGN
 #define DISALLOW_COPY_AND_ASSIGN(T) \
   T(const T&);                      \
   void operator=(const T&)
 #endif
 
-static const malloc_table_t* gMallocTable = nullptr;
+static malloc_table_t gMallocTable;
 
 // Whether DMD finished initializing.
 static bool gIsDMDInitialized = false;
 
 // This provides infallible allocations (they abort on OOM).  We use it for all
 // of DMD's own allocations, which fall into the following three cases.
 //
 // - Direct allocations (the easy case).
@@ -113,88 +113,88 @@ class InfallibleAllocPolicy
   static void ExitOnFailure(const void* aP);
 
 public:
   template <typename T>
   static T* maybe_pod_malloc(size_t aNumElems)
   {
     if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
       return nullptr;
-    return (T*)gMallocTable->malloc(aNumElems * sizeof(T));
+    return (T*)gMallocTable.malloc(aNumElems * sizeof(T));
   }
 
   template <typename T>
   static T* maybe_pod_calloc(size_t aNumElems)
   {
-    return (T*)gMallocTable->calloc(aNumElems, sizeof(T));
+    return (T*)gMallocTable.calloc(aNumElems, sizeof(T));
   }
 
   template <typename T>
   static T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
   {
     if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
       return nullptr;
-    return (T*)gMallocTable->realloc(aPtr, aNewSize * sizeof(T));
+    return (T*)gMallocTable.realloc(aPtr, aNewSize * sizeof(T));
   }
 
   static void* malloc_(size_t aSize)
   {
-    void* p = gMallocTable->malloc(aSize);
+    void* p = gMallocTable.malloc(aSize);
     ExitOnFailure(p);
     return p;
   }
 
   template <typename T>
   static T* pod_malloc(size_t aNumElems)
   {
     T* p = maybe_pod_malloc<T>(aNumElems);
     ExitOnFailure(p);
     return p;
   }
 
   static void* calloc_(size_t aSize)
   {
-    void* p = gMallocTable->calloc(1, aSize);
+    void* p = gMallocTable.calloc(1, aSize);
     ExitOnFailure(p);
     return p;
   }
 
   template <typename T>
   static T* pod_calloc(size_t aNumElems)
   {
     T* p = maybe_pod_calloc<T>(aNumElems);
     ExitOnFailure(p);
     return p;
   }
 
   // This realloc_ is the one we use for direct reallocs within DMD.
   static void* realloc_(void* aPtr, size_t aNewSize)
   {
-    void* p = gMallocTable->realloc(aPtr, aNewSize);
+    void* p = gMallocTable.realloc(aPtr, aNewSize);
     ExitOnFailure(p);
     return p;
   }
 
   // This realloc_ is required for this to be a JS container AllocPolicy.
   template <typename T>
   static T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
   {
     T* p = maybe_pod_realloc(aPtr, aOldSize, aNewSize);
     ExitOnFailure(p);
     return p;
   }
 
   static void* memalign_(size_t aAlignment, size_t aSize)
   {
-    void* p = gMallocTable->memalign(aAlignment, aSize);
+    void* p = gMallocTable.memalign(aAlignment, aSize);
     ExitOnFailure(p);
     return p;
   }
 
-  static void free_(void* aPtr) { gMallocTable->free(aPtr); }
+  static void free_(void* aPtr) { gMallocTable.free(aPtr); }
 
   static char* strdup_(const char* aStr)
   {
     char* s = (char*) InfallibleAllocPolicy::malloc_(strlen(aStr) + 1);
     strcpy(s, aStr);
     return s;
   }
 
@@ -224,17 +224,17 @@ public:
   static void reportAllocOverflow() { ExitOnFailure(nullptr); }
   bool checkSimulatedOOM() const { return true; }
 };
 
 // This is only needed because of the |const void*| vs |void*| arg mismatch.
 static size_t
 MallocSizeOf(const void* aPtr)
 {
-  return gMallocTable->malloc_usable_size(const_cast<void*>(aPtr));
+  return gMallocTable.malloc_usable_size(const_cast<void*>(aPtr));
 }
 
 void
 DMDFuncs::StatusMsg(const char* aFmt, va_list aAp)
 {
 #ifdef ANDROID
     __android_log_vprint(ANDROID_LOG_INFO, "DMD", aFmt, aAp);
 #else
@@ -1215,17 +1215,17 @@ AllocCallback(void* aPtr, size_t aReqSiz
 {
   if (!aPtr) {
     return;
   }
 
   AutoLockState lock;
   AutoBlockIntercepts block(aT);
 
-  size_t actualSize = gMallocTable->malloc_usable_size(aPtr);
+  size_t actualSize = gMallocTable.malloc_usable_size(aPtr);
 
   // We may or may not record the allocation stack trace, depending on the
   // options and the outcome of a Bernoulli trial.
   bool getTrace = gOptions->DoFullStacks() || gBernoulli->trial(actualSize);
   LiveBlock b(aPtr, aReqSize, getTrace ? StackTrace::Get(aT) : nullptr);
   MOZ_ALWAYS_TRUE(gLiveBlockTable->putNew(aPtr, b));
 }
 
@@ -1254,25 +1254,28 @@ FreeCallback(void* aPtr, Thread* aT, Dea
     GCStackTraces();
   }
 }
 
 //---------------------------------------------------------------------------
 // malloc/free interception
 //---------------------------------------------------------------------------
 
-static void Init(const malloc_table_t* aMallocTable);
+static void Init(malloc_table_t* aMallocTable);
 
 } // namespace dmd
 } // namespace mozilla
 
 void
-replace_init(const malloc_table_t* aMallocTable)
+replace_init(malloc_table_t* aMallocTable)
 {
   mozilla::dmd::Init(aMallocTable);
+#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
+#define MALLOC_DECL(name, ...) aMallocTable->name = replace_ ## name;
+#include "malloc_decls.h"
 }
 
 ReplaceMallocBridge*
 replace_get_bridge()
 {
   return mozilla::dmd::gDMDBridge;
 }
 
@@ -1281,58 +1284,58 @@ replace_malloc(size_t aSize)
 {
   using namespace mozilla::dmd;
 
   if (!gIsDMDInitialized) {
     // DMD hasn't started up, either because it wasn't enabled by the user, or
     // we're still in Init() and something has indirectly called malloc.  Do a
     // vanilla malloc.  (In the latter case, if it fails we'll crash.  But
     // OOM is highly unlikely so early on.)
-    return gMallocTable->malloc(aSize);
+    return gMallocTable.malloc(aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     // Intercepts are blocked, which means this must be a call to malloc
     // triggered indirectly by DMD (e.g. via MozStackWalk).  Be infallible.
     return InfallibleAllocPolicy::malloc_(aSize);
   }
 
   // This must be a call to malloc from outside DMD.  Intercept it.
-  void* ptr = gMallocTable->malloc(aSize);
+  void* ptr = gMallocTable.malloc(aSize);
   AllocCallback(ptr, aSize, t);
   return ptr;
 }
 
 void*
 replace_calloc(size_t aCount, size_t aSize)
 {
   using namespace mozilla::dmd;
 
   if (!gIsDMDInitialized) {
-    return gMallocTable->calloc(aCount, aSize);
+    return gMallocTable.calloc(aCount, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::calloc_(aCount * aSize);
   }
 
-  void* ptr = gMallocTable->calloc(aCount, aSize);
+  void* ptr = gMallocTable.calloc(aCount, aSize);
   AllocCallback(ptr, aCount * aSize, t);
   return ptr;
 }
 
 void*
 replace_realloc(void* aOldPtr, size_t aSize)
 {
   using namespace mozilla::dmd;
 
   if (!gIsDMDInitialized) {
-    return gMallocTable->realloc(aOldPtr, aSize);
+    return gMallocTable.realloc(aOldPtr, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::realloc_(aOldPtr, aSize);
   }
 
   // If |aOldPtr| is nullptr, the call is equivalent to |malloc(aSize)|.
@@ -1341,73 +1344,73 @@ replace_realloc(void* aOldPtr, size_t aS
   }
 
   // Be very careful here!  Must remove the block from the table before doing
   // the realloc to avoid races, just like in replace_free().
   // Nb: This does an unnecessary hashtable remove+add if the block doesn't
   // move, but doing better isn't worth the effort.
   DeadBlock db;
   FreeCallback(aOldPtr, t, &db);
-  void* ptr = gMallocTable->realloc(aOldPtr, aSize);
+  void* ptr = gMallocTable.realloc(aOldPtr, aSize);
   if (ptr) {
     AllocCallback(ptr, aSize, t);
     MaybeAddToDeadBlockTable(db);
   } else {
     // If realloc fails, we undo the prior operations by re-inserting the old
     // pointer into the live block table. We don't have to do anything with the
     // dead block list because the dead block hasn't yet been inserted. The
     // block will end up looking like it was allocated for the first time here,
     // which is untrue, and the slop bytes will be zero, which may be untrue.
     // But this case is rare and doing better isn't worth the effort.
-    AllocCallback(aOldPtr, gMallocTable->malloc_usable_size(aOldPtr), t);
+    AllocCallback(aOldPtr, gMallocTable.malloc_usable_size(aOldPtr), t);
   }
   return ptr;
 }
 
 void*
 replace_memalign(size_t aAlignment, size_t aSize)
 {
   using namespace mozilla::dmd;
 
   if (!gIsDMDInitialized) {
-    return gMallocTable->memalign(aAlignment, aSize);
+    return gMallocTable.memalign(aAlignment, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::memalign_(aAlignment, aSize);
   }
 
-  void* ptr = gMallocTable->memalign(aAlignment, aSize);
+  void* ptr = gMallocTable.memalign(aAlignment, aSize);
   AllocCallback(ptr, aSize, t);
   return ptr;
 }
 
 void
 replace_free(void* aPtr)
 {
   using namespace mozilla::dmd;
 
   if (!gIsDMDInitialized) {
-    gMallocTable->free(aPtr);
+    gMallocTable.free(aPtr);
     return;
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::free_(aPtr);
   }
 
   // Do the actual free after updating the table.  Otherwise, another thread
   // could call malloc and get the freed block and update the table, and then
   // our update here would remove the newly-malloc'd block.
   DeadBlock db;
   FreeCallback(aPtr, t, &db);
   MaybeAddToDeadBlockTable(db);
-  gMallocTable->free(aPtr);
+  gMallocTable.free(aPtr);
 }
 
 namespace mozilla {
 namespace dmd {
 
 //---------------------------------------------------------------------------
 // Options (Part 2)
 //---------------------------------------------------------------------------
@@ -1577,19 +1580,19 @@ postfork()
   }
 }
 
 // WARNING: this function runs *very* early -- before all static initializers
 // have run.  For this reason, non-scalar globals such as gStateLock and
 // gStackTraceTable are allocated dynamically (so we can guarantee their
 // construction in this function) rather than statically.
 static void
-Init(const malloc_table_t* aMallocTable)
+Init(malloc_table_t* aMallocTable)
 {
-  gMallocTable = aMallocTable;
+  gMallocTable = *aMallocTable;
   gDMDBridge = InfallibleAllocPolicy::new_<DMDBridge>();
 
 #ifndef XP_WIN
   // Avoid deadlocks when forking by acquiring our state lock prior to forking
   // and releasing it after forking. See |LogAlloc|'s |replace_init| for
   // in-depth details.
   //
   // Note: This must run after attempting an allocation so as to give the
--- a/memory/replace/logalloc/LogAlloc.cpp
+++ b/memory/replace/logalloc/LogAlloc.cpp
@@ -17,17 +17,17 @@
 #include <pthread.h>
 #endif
 
 #include "replace_malloc.h"
 #include "FdPrintf.h"
 
 #include "base/lock.h"
 
-static const malloc_table_t* sFuncs = nullptr;
+static malloc_table_t sFuncs;
 static intptr_t sFd = 0;
 static bool sStdoutOrStderr = false;
 
 static Lock sLock;
 
 static void
 prefork() {
   sLock.Acquire();
@@ -69,19 +69,28 @@ class LogAllocBridge : public ReplaceMal
   virtual void InitDebugFd(mozilla::DebugFdRegistry& aRegistry) override {
     if (!sStdoutOrStderr) {
       aRegistry.RegisterHandle(sFd);
     }
   }
 };
 
 void
-replace_init(const malloc_table_t* aTable)
+replace_init(malloc_table_t* aTable)
 {
-  sFuncs = aTable;
+  sFuncs = *aTable;
+#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC_BASE
+#define MALLOC_DECL(name, ...) aTable->name = replace_ ## name;
+#include "malloc_decls.h"
+  aTable->jemalloc_stats = replace_jemalloc_stats;
+#ifndef LOGALLOC_MINIMAL
+  aTable->posix_memalign = replace_posix_memalign;
+  aTable->aligned_alloc = replace_aligned_alloc;
+  aTable->valloc = replace_valloc;
+#endif
 
 #ifndef _WIN32
   /* When another thread has acquired a lock before forking, the child
    * process will inherit the lock state but the thread, being nonexistent
    * in the child process, will never release it, leading to a dead-lock
    * whenever the child process gets the lock. We thus need to ensure no
    * other thread is holding the lock before forking, by acquiring it
    * ourselves, and releasing it after forking, both in the parent and child
@@ -102,17 +111,17 @@ replace_init(const malloc_table_t* aTabl
    *   - thread B then proceeds to call the real allocator's malloc, and
    *     waits for the real allocator's lock, which thread A holds.
    *   - libc calls our prefork, so thread A waits for our lock, which
    *     thread B holds.
    * To avoid this race condition, the real allocator's prefork must be
    * called after ours, which means it needs to be registered before ours.
    * So trick the real allocator into initializing itself without more side
    * effects by calling malloc with a size it can't possibly allocate. */
-  sFuncs->malloc(-1);
+  sFuncs.malloc(-1);
   pthread_atfork(prefork, postfork, postfork);
 #endif
 
   /* Initialize output file descriptor from the MALLOC_LOG environment
    * variable. Numbers up to 9999 are considered as a preopened file
    * descriptor number. Other values are considered as a file name. */
   char* log = getenv("MALLOC_LOG");
   if (log && *log) {
@@ -170,107 +179,107 @@ replace_get_bridge()
  * Use locking to guarantee that an allocation that did happen is logged
  * before any other allocation/free happens.
  */
 
 void*
 replace_malloc(size_t aSize)
 {
   AutoLock lock(sLock);
-  void* ptr = sFuncs->malloc(aSize);
+  void* ptr = sFuncs.malloc(aSize);
   if (ptr) {
     FdPrintf(sFd, "%zu %zu malloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
   }
   return ptr;
 }
 
 #ifndef LOGALLOC_MINIMAL
 int
 replace_posix_memalign(void** aPtr, size_t aAlignment, size_t aSize)
 {
   AutoLock lock(sLock);
-  int ret = sFuncs->posix_memalign(aPtr, aAlignment, aSize);
+  int ret = sFuncs.posix_memalign(aPtr, aAlignment, aSize);
   if (ret == 0) {
     FdPrintf(sFd, "%zu %zu posix_memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
              aAlignment, aSize, *aPtr);
   }
   return ret;
 }
 
 void*
 replace_aligned_alloc(size_t aAlignment, size_t aSize)
 {
   AutoLock lock(sLock);
-  void* ptr = sFuncs->aligned_alloc(aAlignment, aSize);
+  void* ptr = sFuncs.aligned_alloc(aAlignment, aSize);
   if (ptr) {
     FdPrintf(sFd, "%zu %zu aligned_alloc(%zu,%zu)=%p\n", GetPid(), GetTid(),
              aAlignment, aSize, ptr);
   }
   return ptr;
 }
 #endif
 
 void*
 replace_calloc(size_t aNum, size_t aSize)
 {
   AutoLock lock(sLock);
-  void* ptr = sFuncs->calloc(aNum, aSize);
+  void* ptr = sFuncs.calloc(aNum, aSize);
   if (ptr) {
     FdPrintf(sFd, "%zu %zu calloc(%zu,%zu)=%p\n", GetPid(), GetTid(), aNum,
              aSize, ptr);
   }
   return ptr;
 }
 
 void*
 replace_realloc(void* aPtr, size_t aSize)
 {
   AutoLock lock(sLock);
-  void* new_ptr = sFuncs->realloc(aPtr, aSize);
+  void* new_ptr = sFuncs.realloc(aPtr, aSize);
   if (new_ptr || !aSize) {
     FdPrintf(sFd, "%zu %zu realloc(%p,%zu)=%p\n", GetPid(), GetTid(), aPtr,
              aSize, new_ptr);
   }
   return new_ptr;
 }
 
 void
 replace_free(void* aPtr)
 {
   AutoLock lock(sLock);
   if (aPtr) {
     FdPrintf(sFd, "%zu %zu free(%p)\n", GetPid(), GetTid(), aPtr);
   }
-  sFuncs->free(aPtr);
+  sFuncs.free(aPtr);
 }
 
 void*
 replace_memalign(size_t aAlignment, size_t aSize)
 {
   AutoLock lock(sLock);
-  void* ptr = sFuncs->memalign(aAlignment, aSize);
+  void* ptr = sFuncs.memalign(aAlignment, aSize);
   if (ptr) {
     FdPrintf(sFd, "%zu %zu memalign(%zu,%zu)=%p\n", GetPid(), GetTid(),
              aAlignment, aSize, ptr);
   }
   return ptr;
 }
 
 #ifndef LOGALLOC_MINIMAL
 void*
 replace_valloc(size_t aSize)
 {
   AutoLock lock(sLock);
-  void* ptr = sFuncs->valloc(aSize);
+  void* ptr = sFuncs.valloc(aSize);
   if (ptr) {
     FdPrintf(sFd, "%zu %zu valloc(%zu)=%p\n", GetPid(), GetTid(), aSize, ptr);
   }
   return ptr;
 }
 #endif
 
 void
 replace_jemalloc_stats(jemalloc_stats_t* aStats)
 {
   AutoLock lock(sLock);
-  sFuncs->jemalloc_stats(aStats);
+  sFuncs.jemalloc_stats(aStats);
   FdPrintf(sFd, "%zu %zu jemalloc_stats()\n", GetPid(), GetTid());
 }
--- a/mozglue/build/replace_malloc.mk
+++ b/mozglue/build/replace_malloc.mk
@@ -1,13 +1,7 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC))
-# Technically, ) is not a necessary delimiter in the awk call, but make
-# doesn't like the opening ( there being alone...
-MK_LDFLAGS = \
-  $(shell awk -F'[(),]' '/^MALLOC_DECL/{print "-Wl,-U,_replace_" $$2}' $(topsrcdir)/memory/build/malloc_decls.h) \
-  $(NULL)
-
-EXTRA_DEPS += $(topsrcdir)/memory/build/malloc_decls.h
+MK_LDFLAGS = -Wl,-U,_replace_init -Wl,-U,_replace_get_bridge
 endif