Bug 1097507 - Make libxul independent of libdmd when DMD is enabled. r=njn
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 18 Nov 2014 19:21:06 +0900
changeset 216222 3c6bd176246d277649e7a73dd00f201cfa194fc6
parent 216221 bd1cbced0ccb7963fcda09ede2cd17af4bd41de0
child 216223 3b2c7cb5dabed177b3e4950e10fe621313db8c5f
push id27842
push usercbook@mozilla.com
push dateTue, 18 Nov 2014 16:25:55 +0000
treeherdermozilla-central@084441e904d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1097507
milestone36.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 1097507 - Make libxul independent of libdmd when DMD is enabled. r=njn This also effectively changes how DMD is enabled from requiring both replace-malloc initialization and the DMD environment variable to requiring only the former. The DMD environment variable can still be used to specify options, but not to disable entirely. This however doesn't touch all the parts that do enable DMD by setting the DMD environment variable to 1, so the code to handle this value is kept.
memory/build/replace_malloc_bridge.h
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/test/SmokeDMD.cpp
memory/replace/dmd/test/moz.build
memory/replace/dmd/test/script-sort-by.json.gz
toolkit/library/moz.build
xpcom/base/nsMemoryInfoDumper.cpp
xpcom/tests/TestDeadlockDetectorScalability.cpp
xpcom/tests/moz.build
--- a/memory/build/replace_malloc_bridge.h
+++ b/memory/build/replace_malloc_bridge.h
@@ -53,19 +53,28 @@ struct ReplaceMallocBridge;
 #include "mozilla/NullPtr.h"
 #include "mozilla/Types.h"
 
 #ifndef REPLACE_MALLOC_IMPL
 /* Returns the replace-malloc bridge if there is one to be returned. */
 extern "C" MFBT_API ReplaceMallocBridge* get_bridge();
 #endif
 
+namespace mozilla {
+namespace dmd {
+struct DMDFuncs;
+}
+}
+
 struct ReplaceMallocBridge
 {
-  ReplaceMallocBridge() : mVersion(0) {}
+  ReplaceMallocBridge() : mVersion(1) {}
+
+  /* This method was added in version 1 of the bridge. */
+  virtual mozilla::dmd::DMDFuncs* GetDMDFuncs() { return nullptr; }
 
 #ifndef REPLACE_MALLOC_IMPL
   /* Returns the replace-malloc bridge if its version is at least the
    * requested one. */
   static ReplaceMallocBridge* Get(int aMinimumVersion) {
     static ReplaceMallocBridge* sSingleton = get_bridge();
     return (sSingleton && sSingleton->mVersion >= aMinimumVersion)
       ? sSingleton : nullptr;
@@ -80,14 +89,21 @@ protected:
 /* Class containing wrappers for calls to ReplaceMallocBridge methods.
  * Those wrappers need to be static methods in a class because compilers
  * complain about unused static global functions, and linkers complain
  * about multiple definitions of non-static global functions.
  * Using a separate class from ReplaceMallocBridge allows the function
  * names to be identical. */
 struct ReplaceMalloc
 {
+  /* Don't call this method from performance critical code. Use
+   * mozilla::dmd::DMDFuncs::Get() instead, it has less overhead. */
+  static mozilla::dmd::DMDFuncs* GetDMDFuncs()
+  {
+    auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 1);
+    return singleton ? singleton->GetDMDFuncs() : nullptr;
+  }
 };
 #endif
 
 #endif /* __cplusplus */
 
 #endif /* replace_malloc_bridge_h */
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -1,16 +1,14 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#include "DMD.h"
-
 #include <ctype.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -40,16 +38,18 @@
 #include "mozilla/JSONWriter.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 
 // CodeAddressService is defined entirely in the header, so this does not make
 // DMD depend on XPCOM's object file.
 #include "CodeAddressService.h"
 
+// replace_malloc.h needs to be included before replace_malloc_bridge.h,
+// which DMD.h includes, so DMD.h needs to be included after replace_malloc.h.
 // MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define
 // replace_{posix_memalign,aligned_alloc,valloc}.  It requires defining
 // PAGE_SIZE.  Nb: sysconf() is expensive, but it's only used for (the obsolete
 // and rarely used) valloc.
 #define MOZ_REPLACE_ONLY_MEMALIGN 1
 
 #ifdef XP_WIN
 #define PAGE_SIZE GetPageSize()
@@ -61,33 +61,58 @@ static long GetPageSize()
 }
 #else
 #define PAGE_SIZE sysconf(_SC_PAGESIZE)
 #endif
 #include "replace_malloc.h"
 #undef MOZ_REPLACE_ONLY_MEMALIGN
 #undef PAGE_SIZE
 
+#include "DMD.h"
+
 namespace mozilla {
 namespace dmd {
 
+class DMDBridge : public ReplaceMallocBridge
+{
+  virtual DMDFuncs* GetDMDFuncs() MOZ_OVERRIDE;
+};
+
+static DMDBridge sDMDBridge;
+static DMDFuncs sDMDFuncs;
+
+DMDFuncs*
+DMDBridge::GetDMDFuncs()
+{
+  return &sDMDFuncs;
+}
+
+inline void
+StatusMsg(const char* aFmt, ...)
+{
+  va_list ap;
+  va_start(ap, aFmt);
+  sDMDFuncs.StatusMsg(aFmt, ap);
+  va_end(ap);
+}
+
 //---------------------------------------------------------------------------
 // Utilities
 //---------------------------------------------------------------------------
 
 #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;
 
-// This enables/disables DMD.
-static bool gIsDMDRunning = false;
+// 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).
 // - Indirect allocations in js::{Vector,HashSet,HashMap} -- this class serves
 //   as their AllocPolicy.
 // - Other indirect allocations (e.g. NS_StackWalk) -- see the comments on
 //   Thread::mBlockIntercepts and in replace_malloc for how these work.
@@ -192,36 +217,33 @@ public:
 
 // 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));
 }
 
-MOZ_EXPORT void
-StatusMsg(const char* aFmt, ...)
+void
+DMDFuncs::StatusMsg(const char* aFmt, va_list aAp)
 {
-  va_list ap;
-  va_start(ap, aFmt);
 #ifdef ANDROID
 #ifdef MOZ_B2G_LOADER
   // Don't call __android_log_vprint() during initialization, or the magic file
   // descriptors will be occupied by android logcat.
-  if (gIsDMDRunning)
+  if (gIsDMDInitialized)
 #endif
-    __android_log_vprint(ANDROID_LOG_INFO, "DMD", aFmt, ap);
+    __android_log_vprint(ANDROID_LOG_INFO, "DMD", aFmt, aAp);
 #else
   // The +64 is easily enough for the "DMD[<pid>] " prefix and the NUL.
   char* fmt = (char*) InfallibleAllocPolicy::malloc_(strlen(aFmt) + 64);
   sprintf(fmt, "DMD[%d] %s", getpid(), aFmt);
-  vfprintf(stderr, fmt, ap);
+  vfprintf(stderr, fmt, aAp);
   InfallibleAllocPolicy::free_(fmt);
 #endif
-  va_end(ap);
 }
 
 /* static */ void
 InfallibleAllocPolicy::ExitOnFailure(const void* aP)
 {
   if (!aP) {
     MOZ_CRASH("DMD out of memory; aborting");
   }
@@ -949,18 +971,16 @@ GCStackTraces()
 // malloc/free callbacks
 //---------------------------------------------------------------------------
 
 static size_t gSmallBlockActualSizeCounter = 0;
 
 static void
 AllocCallback(void* aPtr, size_t aReqSize, Thread* aT)
 {
-  MOZ_ASSERT(gIsDMDRunning);
-
   if (!aPtr) {
     return;
   }
 
   AutoLockState lock;
   AutoBlockIntercepts block(aT);
 
   size_t actualSize = gMallocTable->malloc_usable_size(aPtr);
@@ -983,18 +1003,16 @@ AllocCallback(void* aPtr, size_t aReqSiz
     Block b(aPtr, aReqSize, StackTrace::Get(aT), /* sampled */ false);
     (void)gBlockTable->putNew(aPtr, b);
   }
 }
 
 static void
 FreeCallback(void* aPtr, Thread* aT)
 {
-  MOZ_ASSERT(gIsDMDRunning);
-
   if (!aPtr) {
     return;
   }
 
   AutoLockState lock;
   AutoBlockIntercepts block(aT);
 
   gBlockTable->remove(aPtr);
@@ -1014,22 +1032,28 @@ static void Init(const malloc_table_t* a
 }   // namespace mozilla
 
 void
 replace_init(const malloc_table_t* aMallocTable)
 {
   mozilla::dmd::Init(aMallocTable);
 }
 
+ReplaceMallocBridge*
+replace_get_bridge()
+{
+  return &mozilla::dmd::sDMDBridge;
+}
+
 void*
 replace_malloc(size_t aSize)
 {
   using namespace mozilla::dmd;
 
-  if (!gIsDMDRunning) {
+  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);
   }
 
   Thread* t = Thread::Fetch();
@@ -1045,17 +1069,17 @@ replace_malloc(size_t aSize)
   return ptr;
 }
 
 void*
 replace_calloc(size_t aCount, size_t aSize)
 {
   using namespace mozilla::dmd;
 
-  if (!gIsDMDRunning) {
+  if (!gIsDMDInitialized) {
     return gMallocTable->calloc(aCount, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::calloc_(aCount * aSize);
   }
 
@@ -1064,17 +1088,17 @@ replace_calloc(size_t aCount, size_t aSi
   return ptr;
 }
 
 void*
 replace_realloc(void* aOldPtr, size_t aSize)
 {
   using namespace mozilla::dmd;
 
-  if (!gIsDMDRunning) {
+  if (!gIsDMDInitialized) {
     return gMallocTable->realloc(aOldPtr, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::realloc_(aOldPtr, aSize);
   }
 
@@ -1101,17 +1125,17 @@ replace_realloc(void* aOldPtr, size_t aS
   return ptr;
 }
 
 void*
 replace_memalign(size_t aAlignment, size_t aSize)
 {
   using namespace mozilla::dmd;
 
-  if (!gIsDMDRunning) {
+  if (!gIsDMDInitialized) {
     return gMallocTable->memalign(aAlignment, aSize);
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::memalign_(aAlignment, aSize);
   }
 
@@ -1120,17 +1144,17 @@ replace_memalign(size_t aAlignment, size
   return ptr;
 }
 
 void
 replace_free(void* aPtr)
 {
   using namespace mozilla::dmd;
 
-  if (!gIsDMDRunning) {
+  if (!gIsDMDInitialized) {
     gMallocTable->free(aPtr);
     return;
   }
 
   Thread* t = Thread::Fetch();
   if (t->InterceptsAreBlocked()) {
     return InfallibleAllocPolicy::free_(aPtr);
   }
@@ -1261,21 +1285,18 @@ Options::Options(const char* aDMDEnvVar)
 }
 
 void
 Options::BadArg(const char* aArg)
 {
   StatusMsg("\n");
   StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
   StatusMsg("\n");
-  StatusMsg("Valid values of $DMD are:\n");
-  StatusMsg("- undefined or \"\" or \"0\", which disables DMD, or\n");
-  StatusMsg("- \"1\", which enables it with the default options, or\n");
-  StatusMsg("- a whitespace-separated list of |--option=val| entries, which\n");
-  StatusMsg("  enables it with non-default options.\n");
+  StatusMsg("$DMD must be a whitespace-separated list of |--option=val|\n");
+  StatusMsg("entries.\n");
   StatusMsg("\n");
   StatusMsg("The following options are allowed;  defaults are shown in [].\n");
   StatusMsg("  --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
             int(mSampleBelowSize.mMax),
             int(mSampleBelowSize.mDefault));
   StatusMsg("                               (prime numbers are recommended)\n");
   StatusMsg("  --max-frames=<1..%d>         Max. depth of stack traces [%d]\n",
             int(mMaxFrames.mMax),
@@ -1299,28 +1320,23 @@ NopStackWalkCallback(uint32_t aFrameNumb
 
 // 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)
 {
-  MOZ_ASSERT(!gIsDMDRunning);
-
   gMallocTable = aMallocTable;
 
   // DMD is controlled by the |DMD| environment variable.
-  // - If it's unset or empty or "0", DMD doesn't run.
-  // - Otherwise, the contents dictate DMD's behaviour.
+  const char* e = getenv("DMD");
 
-  char* e = getenv("DMD");
-
-  if (!e || strcmp(e, "") == 0 || strcmp(e, "0") == 0) {
-    return;
+  if (!e) {
+    e = "1";
   }
 
   StatusMsg("$DMD = '%s'\n", e);
 
   // Parse $DMD env var.
   gOptions = InfallibleAllocPolicy::new_<Options>(e);
 
 #ifdef XP_MACOSX
@@ -1345,27 +1361,27 @@ Init(const malloc_table_t* aMallocTable)
 
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
     gStackTraceTable->init(8192);
 
     gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
     gBlockTable->init(8192);
   }
 
-  gIsDMDRunning = true;
+  gIsDMDInitialized = true;
 }
 
 //---------------------------------------------------------------------------
 // DMD reporting and unreporting
 //---------------------------------------------------------------------------
 
 static void
 ReportHelper(const void* aPtr, bool aReportedOnAlloc)
 {
-  if (!gIsDMDRunning || !aPtr) {
+  if (!aPtr) {
     return;
   }
 
   Thread* t = Thread::Fetch();
 
   AutoBlockIntercepts block(t);
   AutoLockState lock;
 
@@ -1374,24 +1390,24 @@ ReportHelper(const void* aPtr, bool aRep
   } else {
     // We have no record of the block.  Do nothing.  Either:
     // - We're sampling and we skipped this block.  This is likely.
     // - It's a bogus pointer.  This is unlikely because Report() is almost
     //   always called in conjunction with a malloc_size_of-style function.
   }
 }
 
-MOZ_EXPORT void
-Report(const void* aPtr)
+void
+DMDFuncs::Report(const void* aPtr)
 {
   ReportHelper(aPtr, /* onAlloc */ false);
 }
 
-MOZ_EXPORT void
-ReportOnAlloc(const void* aPtr)
+void
+DMDFuncs::ReportOnAlloc(const void* aPtr)
 {
   ReportHelper(aPtr, /* onAlloc */ true);
 }
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
@@ -1415,20 +1431,16 @@ static const int kOutputVersionNumber = 
 static void
 SizeOfInternal(Sizes* aSizes)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
 
   aSizes->Clear();
 
-  if (!gIsDMDRunning) {
-    return;
-  }
-
   StackTraceSet usedStackTraces;
   GatherUsedStackTraces(usedStackTraces);
 
   for (StackTraceTable::Range r = gStackTraceTable->all();
        !r.empty();
        r.popFront()) {
     StackTrace* const& st = r.front();
 
@@ -1440,53 +1452,39 @@ SizeOfInternal(Sizes* aSizes)
   }
 
   aSizes->mStackTraceTable =
     gStackTraceTable->sizeOfIncludingThis(MallocSizeOf);
 
   aSizes->mBlockTable = gBlockTable->sizeOfIncludingThis(MallocSizeOf);
 }
 
-MOZ_EXPORT void
-SizeOf(Sizes* aSizes)
+void
+DMDFuncs::SizeOf(Sizes* aSizes)
 {
   aSizes->Clear();
 
-  if (!gIsDMDRunning) {
-    return;
-  }
-
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
   SizeOfInternal(aSizes);
 }
 
-MOZ_EXPORT void
-ClearReports()
+void
+DMDFuncs::ClearReports()
 {
-  if (!gIsDMDRunning) {
-    return;
-  }
-
   AutoLockState lock;
 
   // Unreport all blocks that were marked reported by a memory reporter.  This
   // excludes those that were reported on allocation, because they need to keep
   // their reported marking.
   for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
     r.front().UnreportIfNotReportedOnAlloc();
   }
 }
 
-MOZ_EXPORT bool
-IsRunning()
-{
-  return gIsDMDRunning;
-}
-
 class ToIdStringConverter MOZ_FINAL
 {
 public:
   ToIdStringConverter() : mNextId(0) { mIdMap.init(512); }
 
   // Converts a pointer to a unique ID. Reuses the existing ID for the pointer if
   // it's been seen before.
   const char* ToIdString(const void* aPtr)
@@ -1546,20 +1544,16 @@ private:
   uint32_t mNextId;
   static const size_t kIdBufLen = 16;
   char mIdBuf[kIdBufLen];
 };
 
 static void
 AnalyzeReportsImpl(UniquePtr<JSONWriteFunc> aWriter)
 {
-  if (!gIsDMDRunning) {
-    return;
-  }
-
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
 
   // Allocate this on the heap instead of the stack because it's fairly large.
   auto locService = InfallibleAllocPolicy::new_<CodeAddressService>();
 
   StackTraceSet usedStackTraces;
   usedStackTraces.init(512);
@@ -1722,34 +1716,34 @@ AnalyzeReportsImpl(UniquePtr<JSONWriteFu
     StatusMsg("  }\n");
   }
 
   InfallibleAllocPolicy::delete_(locService);
 
   StatusMsg("}\n");
 }
 
-MOZ_EXPORT void
-AnalyzeReports(UniquePtr<JSONWriteFunc> aWriter)
+void
+DMDFuncs::AnalyzeReports(UniquePtr<JSONWriteFunc> aWriter)
 {
   AnalyzeReportsImpl(Move(aWriter));
   ClearReports();
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
-MOZ_EXPORT void
-SetSampleBelowSize(size_t aSize)
+void
+DMDFuncs::SetSampleBelowSize(size_t aSize)
 {
   gOptions->SetSampleBelowSize(aSize);
 }
 
-MOZ_EXPORT void
-ClearBlocks()
+void
+DMDFuncs::ClearBlocks()
 {
   gBlockTable->clear();
   gSmallBlockActualSizeCounter = 0;
 }
 
 }   // namespace dmd
 }   // namespace mozilla
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -3,42 +3,142 @@
 /* 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/. */
 
 #ifndef DMD_h___
 #define DMD_h___
 
 #include <string.h>
+#include <stdarg.h>
 
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
 #include "mozilla/Types.h"
 #include "mozilla/UniquePtr.h"
 
+#include "replace_malloc_bridge.h"
+
 namespace mozilla {
 
 class JSONWriteFunc;
 
 namespace dmd {
 
+struct Sizes
+{
+  size_t mStackTracesUsed;
+  size_t mStackTracesUnused;
+  size_t mStackTraceTable;
+  size_t mBlockTable;
+
+  Sizes() { Clear(); }
+  void Clear() { memset(this, 0, sizeof(Sizes)); }
+};
+
+// See further below for a description of each method. The DMDFuncs class
+// should contain a virtual method for each of them (except IsRunning,
+// which can be inferred from the DMDFuncs singleton existing).
+struct DMDFuncs
+{
+  virtual void Report(const void*);
+
+  virtual void ReportOnAlloc(const void*);
+
+  virtual void ClearReports();
+
+  virtual void AnalyzeReports(UniquePtr<JSONWriteFunc>);
+
+  virtual void SizeOf(Sizes*);
+
+  virtual void StatusMsg(const char*, va_list);
+
+  virtual void SetSampleBelowSize(size_t);
+
+  virtual void ClearBlocks();
+
+#ifndef REPLACE_MALLOC_IMPL
+  // We deliberately don't use ReplaceMalloc::GetDMDFuncs here, because if we
+  // did, the following would happen.
+  // - The code footprint of each call to Get() larger as GetDMDFuncs ends
+  //   up inlined.
+  // - When no replace-malloc library is loaded, the number of instructions
+  //   executed is equivalent, but don't necessarily fit in the same cache
+  //   line.
+  // - When a non-DMD replace-malloc library is loaded, the overhead is
+  //   higher because there is first a check for the replace malloc bridge
+  //   and then for the DMDFuncs singleton.
+  // Initializing the DMDFuncs singleton on the first access makes the
+  // overhead even worse. Either Get() is inlined and massive, or it isn't
+  // and a simple value check becomes a function call.
+  static DMDFuncs* Get() { return sSingleton.Get(); }
+
+private:
+  // Wrapper class keeping a pointer to the DMD functions. It is statically
+  // initialized because it needs to be set early enough.
+  // Debug builds also check that it's never accessed before the static
+  // initialization actually occured, which could be the case if some other
+  // static initializer ended up calling into DMD.
+  class Singleton
+  {
+  public:
+    Singleton() : mValue(ReplaceMalloc::GetDMDFuncs()), mInitialized(true) {}
+
+    DMDFuncs* Get()
+    {
+      MOZ_ASSERT(mInitialized);
+      return mValue;
+    }
+
+  private:
+    DMDFuncs* mValue;
+    DebugOnly<bool> mInitialized;
+  };
+
+  // This singleton pointer must be defined on the program side. In Gecko,
+  // this is done in xpcom/base/nsMemoryInfoDumper.cpp.
+  static /* DMDFuncs:: */Singleton sSingleton;
+#endif
+};
+
+#ifndef REPLACE_MALLOC_IMPL
 // Mark a heap block as reported by a memory reporter.
-MOZ_EXPORT void
-Report(const void* aPtr);
+inline void
+Report(const void* aPtr)
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->Report(aPtr);
+  }
+}
 
 // Mark a heap block as reported immediately on allocation.
-MOZ_EXPORT void
-ReportOnAlloc(const void* aPtr);
+inline void
+ReportOnAlloc(const void* aPtr)
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->ReportOnAlloc(aPtr);
+  }
+}
 
 // Clears existing reportedness data from any prior runs of the memory
 // reporters.  The following sequence should be used.
 // - ClearReports()
 // - run the memory reporters
 // - AnalyzeReports()
 // This sequence avoids spurious twice-reported warnings.
-MOZ_EXPORT void
-ClearReports();
+inline void
+ClearReports()
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->ClearReports();
+  }
+}
 
 // Determines which heap blocks have been reported, and dumps JSON output
 // (via |aWriter|) describing the heap.
 //
 // The following sample output contains comments that explain the format and
 // design choices. The output files can be quite large, so a number of
 // decisions were made to minimize size, such as using short property names and
 // omitting properties whenever possible.
@@ -116,47 +216,79 @@ ClearReports();
 //     // that output stack traces in a conventional non-shared format.
 //     "D": "#00: foo (Foo.cpp:123)",
 //     "E": "#00: bar (Bar.cpp:234)",
 //     "F": "#00: baz (Baz.cpp:345)",
 //     "G": "#00: quux (Quux.cpp:456)",
 //     "H": "#00: quuux (Quux.cpp:567)"
 //   }
 // }
-MOZ_EXPORT void
-AnalyzeReports(mozilla::UniquePtr<mozilla::JSONWriteFunc>);
-
-struct Sizes
+// Implementation note: normally, this wouldn't be templated, but in that case,
+// the function is compiled, which makes the destructor for the UniquePtr fire up,
+// and that needs JSONWriteFunc to be fully defined. That, in turn, requires to
+// include JSONWriter.h, which includes double-conversion.h, which ends up breaking
+// various things built with -Werror for various reasons.
+template <typename JSONWriteFunc>
+inline void
+AnalyzeReports(UniquePtr<JSONWriteFunc> aWriteFunc)
 {
-  size_t mStackTracesUsed;
-  size_t mStackTracesUnused;
-  size_t mStackTraceTable;
-  size_t mBlockTable;
-
-  Sizes() { Clear(); }
-  void Clear() { memset(this, 0, sizeof(Sizes)); }
-};
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->AnalyzeReports(Move(aWriteFunc));
+  }
+}
 
 // Gets the size of various data structures.  Used to implement a memory
 // reporter for DMD.
-MOZ_EXPORT void
-SizeOf(Sizes* aSizes);
+inline void
+SizeOf(Sizes* aSizes)
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->SizeOf(aSizes);
+  }
+}
 
 // Prints a status message prefixed with "DMD[<pid>]". Use sparingly.
-MOZ_EXPORT void
-StatusMsg(const char* aFmt, ...);
+inline void
+StatusMsg(const char* aFmt, ...)
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    va_list ap;
+    va_start(ap, aFmt);
+    funcs->StatusMsg(aFmt, ap);
+    va_end(ap);
+  }
+}
 
 // Indicates whether or not DMD is running.
-MOZ_EXPORT bool
-IsRunning();
+inline bool
+IsRunning()
+{
+  return !!DMDFuncs::Get();
+}
 
 // Sets the sample-below size. Only used for testing purposes.
-MOZ_EXPORT void
-SetSampleBelowSize(size_t aSize);
+inline void
+SetSampleBelowSize(size_t aSize)
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->SetSampleBelowSize(aSize);
+  }
+}
 
 // Clears all records of live allocations. Only used for testing purposes.
-MOZ_EXPORT void
-ClearBlocks();
+inline void
+ClearBlocks()
+{
+  DMDFuncs* funcs = DMDFuncs::Get();
+  if (funcs) {
+    funcs->ClearBlocks();
+  }
+}
+#endif
 
+} // namespace dmd
 } // namespace mozilla
-} // namespace dmd
 
 #endif /* DMD_h___ */
--- a/memory/replace/dmd/test/SmokeDMD.cpp
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -21,16 +21,18 @@
 #include "mozilla/JSONWriter.h"
 #include "mozilla/UniquePtr.h"
 #include "DMD.h"
 
 using mozilla::JSONWriter;
 using mozilla::MakeUnique;
 using namespace mozilla::dmd;
 
+DMDFuncs::Singleton DMDFuncs::sSingleton;
+
 class FpWriteFunc : public mozilla::JSONWriteFunc
 {
 public:
   explicit FpWriteFunc(const char* aFilename)
   {
     mFp = fopen(aFilename, "w");
     if (!mFp) {
       fprintf(stderr, "SmokeDMD: can't create %s file: %s\n",
--- a/memory/replace/dmd/test/moz.build
+++ b/memory/replace/dmd/test/moz.build
@@ -13,14 +13,12 @@ if CONFIG['OS_ARCH'] == 'WINNT':
     CXXFLAGS += ['-Og-']
 else:
     CXXFLAGS += ['-O0']
 
 DEFINES['MOZ_NO_MOZALLOC'] = True
 
 DISABLE_STL_WRAPPING = True
 
-USE_LIBS += ['dmd']
-
 XPCSHELL_TESTS_MANIFESTS += [
     'xpcshell.ini',
 ]
 
index 709e27ed21d26ee7b2d934e2933185b5ea138c75..f8b602a7359a80984fffd76099696733288788a8
GIT binary patch
literal 267
zc$@(X0rdVKiwFQ;h-Fj)|IJcOPlPZKz4up4W-pi!Q*^Tmx3=--P2<II=(05-TPPNS
ziDdt~Q+7q*2dH!E`<T~E@&*Rj8{d|$#tf2}U|ILB(A}9C7yurs!$aM_Xio$t7zeJ^
zRb!1b)_pvcpC&9bIEDSab;a?qY&+s!$vU7n@ATgCbQc3^OL_<r80^0z-^W0+l4trY
z=bYAC>zZ)MDoifJ{)su+jYlnJWuCcj_%{8CCUiHCR?Fx=G&9IUi8`+f^Q`yQe3wv!
zI-wAlkNuM)l=1@A2$jP8(!w{dt5rZqzzyda2#CaW(KK0_UY+YK<s?do<Wf{>5*0*h
R66q^;VqeUm2{A(h007$kfOP->
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -106,21 +106,16 @@ USE_LIBS += [
     'js',
 ]
 
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
     USE_LIBS += [
         'sandboxbroker',
     ]
 
-if CONFIG['MOZ_DMD']:
-    USE_LIBS += [
-        'dmd',
-    ]
-
 USE_LIBS += [
     'gkmedias',
     'mozalloc',
     'nspr',
     'nss',
     'sqlite',
     'zlib',
 ]
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -755,16 +755,18 @@ nsMemoryInfoDumper::DumpMemoryInfoToTemp
   nsRefPtr<TempDirFinishCallback> finishDumping =
     new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
 
   return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
                               aAnonymize, aMinimizeMemoryUsage, identifier);
 }
 
 #ifdef MOZ_DMD
+dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
+
 nsresult
 nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
                                 FILE** aOutFile)
 {
   if (!dmd::IsRunning()) {
     *aOutFile = nullptr;
     return NS_OK;
   }
--- a/xpcom/tests/TestDeadlockDetectorScalability.cpp
+++ b/xpcom/tests/TestDeadlockDetectorScalability.cpp
@@ -1,14 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: sw=4 ts=4 et :
  * 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/. */
 
+// Avoid DMD-specific parts of MOZ_DEFINE_MALLOC_SIZE_OF
+#undef MOZ_DMD
+
 #include "TestHarness.h"
 #include "nsIMemoryReporter.h"
 
 //#define OLD_API
 
 #define PASS()                                  \
     do {                                        \
         passed(__FUNCTION__);                   \
--- a/xpcom/tests/moz.build
+++ b/xpcom/tests/moz.build
@@ -117,13 +117,8 @@ XPIDL_SOURCES += [
 
 LOCAL_INCLUDES += [
     '../ds',
 ]
 
 RESOURCE_FILES += [
     'test.properties',
 ]
-
-if CONFIG['MOZ_DMD']:
-    USE_LIBS += [
-        'dmd'
-    ]