Bug 822148 (part 2) - DMD: Treat twice-reported blocks more like other blocks. r=jlebar.
authorNicholas Nethercote <nnethercote@mozilla.com>
Mon, 17 Dec 2012 19:40:07 -0800
changeset 125702 feefe01d430bca648db4b4153a285608f7ac89df
parent 125701 360af7a0a75469e8d292dcce736b61dd07c05c5b
child 125703 e71e1028860eafa88e63c8fc41ba7080ddf5366b
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar
bugs822148
milestone20.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 822148 (part 2) - DMD: Treat twice-reported blocks more like other blocks. r=jlebar.
memory/replace/dmd/DMD.cpp
memory/replace/dmd/DMD.h
memory/replace/dmd/test-expected.dmd
xpcom/base/nsMemoryReporterManager.cpp
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -767,18 +767,17 @@ StackTrace::Print(const Writer& aWriter,
 
   if (gMode == Test) {
     // Don't print anything because there's too much variation.
     W("   (stack omitted due to test mode)\n");
     return;
   }
 
   for (uint32_t i = 0; i < mLength; i++) {
-    void* pc = mPcs[i];
-    aLocService->WriteLocation(aWriter, pc);
+    aLocService->WriteLocation(aWriter, Pc(i));
   }
 }
 
 /* static */ const StackTrace*
 StackTrace::Get(Thread* aT)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(aT->interceptsAreBlocked());
@@ -810,85 +809,177 @@ StackTrace::Get(Thread* aT)
   }
   return *p;
 }
 
 //---------------------------------------------------------------------------
 // Heap blocks
 //---------------------------------------------------------------------------
 
+// This class combines a 2-byte-aligned pointer (i.e. one whose bottom bit
+// is zero) with a 1-bit tag.
+//
+// |T| is the pointer type, e.g. |int*|, not the pointed-to type.  This makes
+// is easier to have const pointers, e.g. |TaggedPtr<const int*>|.
+template <typename T>
+class TaggedPtr
+{
+  union
+  {
+    T         mPtr;
+    uintptr_t mUint;
+  };
+
+  static const uintptr_t kTagMask = uintptr_t(0x1);
+  static const uintptr_t kPtrMask = ~kTagMask;
+
+  static bool IsTwoByteAligned(T aPtr)
+  {
+    return (uintptr_t(aPtr) & kTagMask) == 0;
+  }
+
+public:
+  TaggedPtr()
+    : mPtr(nullptr)
+  {}
+
+  TaggedPtr(T aPtr, bool aBool)
+    : mPtr(aPtr)
+  {
+    MOZ_ASSERT(IsTwoByteAligned(aPtr));
+    uintptr_t tag = uintptr_t(aBool);
+    MOZ_ASSERT(tag <= kTagMask);
+    mUint |= (tag & kTagMask);
+  }
+
+  void Set(T aPtr, bool aBool)
+  {
+    MOZ_ASSERT(IsTwoByteAligned(aPtr));
+    mPtr = aPtr;
+    uintptr_t tag = uintptr_t(aBool);
+    MOZ_ASSERT(tag <= kTagMask);
+    mUint |= (tag & kTagMask);
+  }
+
+  T Ptr() const { return reinterpret_cast<T>(mUint & kPtrMask); }
+
+  bool Tag() const { return bool(mUint & kTagMask); }
+};
+
 // A live heap block.
 class LiveBlock
 {
   const void*  mPtr;
-
-  // This assumes that we'll never request an allocation of 2 GiB or more on
-  // 32-bit platforms.
-  static const size_t kReqBits = sizeof(size_t) * 8 - 1;    // 31 or 63
-  const size_t mReqSize:kReqBits; // size requested
-  const size_t mSampled:1;        // was this block sampled? (if so, slop == 0)
-
-public:
-  const StackTrace* const mAllocStackTrace;     // never null
-
-  // Live blocks can be reported in two ways.
-  // - The most common is via a memory reporter traversal -- the block is
-  //   reported when the reporter runs, causing DMD to mark it as reported,
-  //   and DMD must clear the marking once it has finished its analysis.
-  // - Less common are ones that are reported immediately on allocation.  DMD
-  //   must *not* clear the markings of these blocks once it has finished its
-  //   analysis.  The |mReportedOnAlloc| field is set for such blocks.
+  const size_t mReqSize;    // size requested
+
+  // Ptr: |mAllocStackTrace| - stack trace where this block was allocated.
+  // Tag bit 0: |mSampled| - was this block sampled? (if so, slop == 0).
+  TaggedPtr<const StackTrace* const>
+    mAllocStackTrace_mSampled;
+
+  // This array has two elements because we record at most two reports of a
+  // block.
+  // - Ptr: |mReportStackTrace| - stack trace where this block was reported.
+  //   nullptr if not reported.
+  // - Tag bit 0: |mReportedOnAlloc| - was the block reported immediately on
+  //   allocation?  If so, DMD must not clear the report at the end of Dump().
+  //   Only relevant if |mReportStackTrace| is non-nullptr.
   //
-  // |mPtr| is used as the key in LiveBlockTable, so it's ok for these fields
+  // |mPtr| is used as the key in LiveBlockTable, so it's ok for this member
   // to be |mutable|.
-private:
-  mutable const StackTrace* mReportStackTrace;  // nullptr if unreported
-  mutable bool              mReportedOnAlloc;   // true if block was reported
-                                                //   immediately after alloc
+  mutable TaggedPtr<const StackTrace*> mReportStackTrace_mReportedOnAlloc[2];
 
 public:
   LiveBlock(const void* aPtr, size_t aReqSize,
             const StackTrace* aAllocStackTrace, bool aSampled)
     : mPtr(aPtr),
       mReqSize(aReqSize),
-      mSampled(aSampled),
-      mAllocStackTrace(aAllocStackTrace),
-      mReportStackTrace(nullptr),
-      mReportedOnAlloc(false)
- {
-    if (mReqSize != aReqSize)
-    {
-      MOZ_CRASH();              // overflowed mReqSize
-    }
-    MOZ_ASSERT(mAllocStackTrace);
+      mAllocStackTrace_mSampled(aAllocStackTrace, aSampled),
+      mReportStackTrace_mReportedOnAlloc()     // all fields get zeroed
+  {
+    MOZ_ASSERT(aAllocStackTrace);
   }
 
   size_t ReqSize() const { return mReqSize; }
 
   // Sampled blocks always have zero slop.
   size_t SlopSize() const
   {
-    return mSampled ? 0 : MallocSizeOf(mPtr) - mReqSize;
+    return IsSampled() ? 0 : MallocSizeOf(mPtr) - mReqSize;
   }
 
   size_t UsableSize() const
   {
-    return mSampled ? mReqSize : MallocSizeOf(mPtr);
+    return IsSampled() ? mReqSize : MallocSizeOf(mPtr);
+  }
+
+  bool IsSampled() const
+  {
+    return mAllocStackTrace_mSampled.Tag();
+  }
+
+  const StackTrace* AllocStackTrace() const
+  {
+    return mAllocStackTrace_mSampled.Ptr();
+  }
+
+  const StackTrace* ReportStackTrace1() const {
+    return mReportStackTrace_mReportedOnAlloc[0].Ptr();
+  }
+
+  const StackTrace* ReportStackTrace2() const {
+    return mReportStackTrace_mReportedOnAlloc[1].Ptr();
   }
 
-  bool IsSampled() const { return mSampled; }
-
-  bool IsReported() const { return !!mReportStackTrace; }
-
-  const StackTrace* ReportStackTrace() const { return mReportStackTrace; }
+  bool ReportedOnAlloc1() const {
+    return mReportStackTrace_mReportedOnAlloc[0].Tag();
+  }
+
+  bool ReportedOnAlloc2() const {
+    return mReportStackTrace_mReportedOnAlloc[1].Tag();
+  }
+
+  uint32_t NumReports() const {
+    if (ReportStackTrace2()) {
+      MOZ_ASSERT(ReportStackTrace1());
+      return 2;
+    }
+    if (ReportStackTrace1()) {
+      return 1;
+    }
+    return 0;
+  }
 
   // This is |const| thanks to the |mutable| fields above.
-  void Report(Thread* aT, bool aReportedOnAlloc) const;
-
-  void UnreportIfNotReportedOnAlloc() const;
+  void Report(Thread* aT, bool aReportedOnAlloc) const
+  {
+    // We don't bother recording reports after the 2nd one.
+    uint32_t numReports = NumReports();
+    if (numReports < 2) {
+      mReportStackTrace_mReportedOnAlloc[numReports].Set(StackTrace::Get(aT),
+                                                         aReportedOnAlloc);
+    }
+  }
+
+  void UnreportIfNotReportedOnAlloc() const
+  {
+    if (!ReportedOnAlloc1() && !ReportedOnAlloc2()) {
+      mReportStackTrace_mReportedOnAlloc[0].Set(nullptr, 0);
+      mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0);
+
+    } else if (!ReportedOnAlloc1() && ReportedOnAlloc2()) {
+      // Shift the 2nd report down to the 1st one.
+      mReportStackTrace_mReportedOnAlloc[0] =
+        mReportStackTrace_mReportedOnAlloc[1];
+      mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0);
+
+    } else if (ReportedOnAlloc1() && !ReportedOnAlloc2()) {
+      mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0);
+    }
+  }
 
   // Hash policy.
 
   typedef const void* Lookup;
 
   static uint32_t hash(const void* const& aPtr)
   {
     return mozilla::HashGeneric(aPtr);
@@ -1097,91 +1188,48 @@ replace_free(void* aPtr)
   FreeCallback(aPtr, t);
   gMallocTable->free(aPtr);
 }
 
 namespace mozilla {
 namespace dmd {
 
 //---------------------------------------------------------------------------
-// Live and double-report block groups
+// Block groups
 //---------------------------------------------------------------------------
 
-class LiveBlockKey
+class BlockGroupKey
 {
 public:
   const StackTrace* const mAllocStackTrace;   // never null
 protected:
-  const StackTrace* const mReportStackTrace;  // nullptr if unreported
+  const StackTrace* const mReportStackTrace1; // nullptr if unreported
+  const StackTrace* const mReportStackTrace2; // nullptr if not 2x-reported
 
 public:
-  LiveBlockKey(const LiveBlock& aB)
-    : mAllocStackTrace(aB.mAllocStackTrace),
-      mReportStackTrace(aB.ReportStackTrace())
+  BlockGroupKey(const LiveBlock& aB)
+    : mAllocStackTrace(aB.AllocStackTrace()),
+      mReportStackTrace1(aB.ReportStackTrace1()),
+      mReportStackTrace2(aB.ReportStackTrace2())
   {
     MOZ_ASSERT(mAllocStackTrace);
   }
 
-  bool IsReported() const
-  {
-    return !!mReportStackTrace;
-  }
-
   // Hash policy.
 
-  typedef LiveBlockKey Lookup;
-
-  static uint32_t hash(const LiveBlockKey& aKey)
-  {
-    return mozilla::HashGeneric(aKey.mAllocStackTrace,
-                                aKey.mReportStackTrace);
-  }
-
-  static bool match(const LiveBlockKey& aA, const LiveBlockKey& aB)
-  {
-    return aA.mAllocStackTrace  == aB.mAllocStackTrace &&
-           aA.mReportStackTrace == aB.mReportStackTrace;
-  }
-};
-
-class DoubleReportBlockKey
-{
-public:
-  const StackTrace* const mAllocStackTrace;     // never null
-
-protected:
-  // When double-reports occur we record (and later print) the stack trace
-  // of *both* the reporting locations.
-  const StackTrace* const mReportStackTrace1;   // never null
-  const StackTrace* const mReportStackTrace2;   // never null
-
-public:
-  DoubleReportBlockKey(const StackTrace* aAllocStackTrace,
-                       const StackTrace* aReportStackTrace1,
-                       const StackTrace* aReportStackTrace2)
-    : mAllocStackTrace(aAllocStackTrace),
-      mReportStackTrace1(aReportStackTrace1),
-      mReportStackTrace2(aReportStackTrace2)
-  {
-    MOZ_ASSERT(mAllocStackTrace && mReportStackTrace1 && mReportStackTrace2);
-  }
-
-  // Hash policy.
-
-  typedef DoubleReportBlockKey Lookup;
-
-  static uint32_t hash(const DoubleReportBlockKey& aKey)
+  typedef BlockGroupKey Lookup;
+
+  static uint32_t hash(const BlockGroupKey& aKey)
   {
     return mozilla::HashGeneric(aKey.mAllocStackTrace,
                                 aKey.mReportStackTrace1,
                                 aKey.mReportStackTrace2);
   }
 
-  static bool match(const DoubleReportBlockKey& aA,
-                    const DoubleReportBlockKey& aB)
+  static bool match(const BlockGroupKey& aA, const BlockGroupKey& aB)
   {
     return aA.mAllocStackTrace   == aB.mAllocStackTrace &&
            aA.mReportStackTrace1 == aB.mReportStackTrace1 &&
            aA.mReportStackTrace2 == aB.mReportStackTrace2;
   }
 };
 
 class GroupSize
@@ -1216,93 +1264,86 @@ public:
   {
     mReq  += aGroupSize.Req();
     mSlop += aGroupSize.Slop();
     mSampled = mSampled || aGroupSize.IsSampled();
   }
 
   static int Cmp(const GroupSize& aA, const GroupSize& aB)
   {
-    // Primary sort: put bigger usable sizes before smaller usable sizes.
+    // Primary sort: put bigger usable sizes first.
     if (aA.Usable() > aB.Usable()) return -1;
     if (aA.Usable() < aB.Usable()) return  1;
 
-    // Secondary sort: put non-sampled groups before sampled groups.
+    // Secondary sort: put bigger requested sizes first.
+    if (aA.Req() > aB.Req()) return -1;
+    if (aA.Req() < aB.Req()) return  1;
+
+    // Tertiary sort: put non-sampled groups before sampled groups.
     if (!aA.mSampled &&  aB.mSampled) return -1;
     if ( aA.mSampled && !aB.mSampled) return  1;
 
     return 0;
   }
 };
 
-class BlockGroup
+// A group of one or more heap blocks with a common BlockGroupKey.
+class BlockGroup : public BlockGroupKey
 {
-protected:
-  // {Live,DoubleReport}BlockKey serve as the key in
-  // {Live,DoubleReport}BlockGroupTable.  Thes two fields constitute the value,
-  // so it's ok for them to be |mutable|.
+  friend class FrameGroup;      // FrameGroups are created from BlockGroups
+
+  // The BlockGroupKey base class serves as the key in BlockGroupTables.  These
+  // two fields constitute the value, so it's ok for them to be |mutable|.
   mutable uint32_t  mNumBlocks;     // number of blocks with this LiveBlockKey
   mutable GroupSize mGroupSize;     // combined size of those blocks
 
 public:
-  BlockGroup()
-    : mNumBlocks(0),
+  explicit BlockGroup(const BlockGroupKey& aKey)
+    : BlockGroupKey(aKey),
+      mNumBlocks(0),
       mGroupSize()
   {}
 
   const GroupSize& GetGroupSize() const { return mGroupSize; }
 
   // This is |const| thanks to the |mutable| fields above.
   void Add(const LiveBlock& aB) const
   {
     mNumBlocks++;
     mGroupSize.Add(aB);
   }
 
   static const char* const kName;   // for PrintSortedGroups
-};
-
-const char* const BlockGroup::kName = "block";
-
-// A group of one or more live heap blocks with a common LiveBlockKey.
-class LiveBlockGroup : public LiveBlockKey, public BlockGroup
-{
-  friend class FrameGroup;      // FrameGroups are created from LiveBlockGroups
-
-public:
-  explicit LiveBlockGroup(const LiveBlockKey& aKey)
-    : LiveBlockKey(aKey),
-      BlockGroup()
-  {}
 
   void Print(const Writer& aWriter, LocationService* aLocService,
              uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
-    const LiveBlockGroup* const a =
-      *static_cast<const LiveBlockGroup* const*>(aA);
-    const LiveBlockGroup* const b =
-      *static_cast<const LiveBlockGroup* const*>(aB);
+    const BlockGroup* const a =
+      *static_cast<const BlockGroup* const*>(aA);
+    const BlockGroup* const b =
+      *static_cast<const BlockGroup* const*>(aB);
 
     return GroupSize::Cmp(a->mGroupSize, b->mGroupSize);
   }
 };
 
-typedef js::HashSet<LiveBlockGroup, LiveBlockGroup, InfallibleAllocPolicy>
-        LiveBlockGroupTable;
+const char* const BlockGroup::kName = "block";
+
+typedef js::HashSet<BlockGroup, BlockGroup, InfallibleAllocPolicy>
+        BlockGroupTable;
 
 void
-LiveBlockGroup::Print(const Writer& aWriter, LocationService* aLocService,
-                      uint32_t aM, uint32_t aN,
-                      const char* aStr, const char* astr,
-                      size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
-                      size_t aTotalUsableSize) const
+BlockGroup::Print(const Writer& aWriter, LocationService* aLocService,
+                  uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
+                  size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
+                  size_t aTotalUsableSize) const
 {
   bool showTilde = mGroupSize.IsSampled();
 
   W("%s: %s block%s in block group %s of %s\n",
     aStr,
     Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
     Show(aM, gBuf2, kBufLen),
     Show(aN, gBuf3, kBufLen));
@@ -1318,84 +1359,24 @@ LiveBlockGroup::Print(const Writer& aWri
     Percent(aCumulativeUsableSize, aTotalUsableSize),
     Percent(mGroupSize.Usable(), aCategoryUsableSize),
     astr,
     Percent(aCumulativeUsableSize, aCategoryUsableSize));
 
   W(" Allocated at\n");
   mAllocStackTrace->Print(aWriter, aLocService);
 
-  if (IsReported()) {
+  if (mReportStackTrace1) {
     W("\n Reported at\n");
-    mReportStackTrace->Print(aWriter, aLocService);
-  }
-
-  W("\n");
-}
-
-// A group of one or more double-reported heap blocks with a common
-// DoubleReportBlockKey.
-class DoubleReportBlockGroup : public DoubleReportBlockKey, public BlockGroup
-{
-public:
-  explicit DoubleReportBlockGroup(const DoubleReportBlockKey& aKey)
-    : DoubleReportBlockKey(aKey),
-      BlockGroup()
-  {}
-
-  void Print(const Writer& aWriter, LocationService* aLocService,
-             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
-             size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
-             size_t aTotalUsableSize) const;
-
-  static int QsortCmp(const void* aA, const void* aB)
-  {
-    const DoubleReportBlockGroup* const a =
-      *static_cast<const DoubleReportBlockGroup* const*>(aA);
-    const DoubleReportBlockGroup* const b =
-      *static_cast<const DoubleReportBlockGroup* const*>(aB);
-
-    return GroupSize::Cmp(a->mGroupSize, b->mGroupSize);
+    mReportStackTrace1->Print(aWriter, aLocService);
   }
-};
-
-typedef js::HashSet<DoubleReportBlockGroup, DoubleReportBlockGroup,
-                    InfallibleAllocPolicy> DoubleReportBlockGroupTable;
-DoubleReportBlockGroupTable* gDoubleReportBlockGroupTable = nullptr;
-
-void
-DoubleReportBlockGroup::Print(const Writer& aWriter,
-                              LocationService* aLocService,
-                              uint32_t aM, uint32_t aN,
-                              const char* aStr, const char* astr,
-                              size_t aCategoryUsableSize,
-                              size_t aCumulativeUsableSize,
-                              size_t aTotalUsableSize) const
-{
-  bool showTilde = mGroupSize.IsSampled();
-
-  W("%s: %s block%s in block group %s of %s\n",
-    aStr,
-    Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
-    Show(aM, gBuf2, kBufLen),
-    Show(aN, gBuf3, kBufLen));
-
-  W(" %s bytes (%s requested / %s slop)\n",
-    Show(mGroupSize.Usable(), gBuf1, kBufLen, showTilde),
-    Show(mGroupSize.Req(),    gBuf2, kBufLen, showTilde),
-    Show(mGroupSize.Slop(),   gBuf3, kBufLen, showTilde));
-
-  W(" Allocated at\n");
-  mAllocStackTrace->Print(aWriter, aLocService);
-
-  W("\n Previously reported at\n");
-  mReportStackTrace1->Print(aWriter, aLocService);
-
-  W("\n Now reported at\n");
-  mReportStackTrace2->Print(aWriter, aLocService);
+  if (mReportStackTrace2) {
+    W("\n Reported again at\n");
+    mReportStackTrace2->Print(aWriter, aLocService);
+  }
 
   W("\n");
 }
 
 //---------------------------------------------------------------------------
 // Stack frame groups
 //---------------------------------------------------------------------------
 
@@ -1416,17 +1397,17 @@ public:
       mNumBlocks(0),
       mNumBlockGroups(0),
       mGroupSize()
   {}
 
   const GroupSize& GetGroupSize() const { return mGroupSize; }
 
   // This is |const| thanks to the |mutable| fields above.
-  void Add(const LiveBlockGroup& aBg) const
+  void Add(const BlockGroup& aBg) const
   {
     mNumBlocks += aBg.mNumBlocks;
     mNumBlockGroups++;
     mGroupSize.Add(aBg.mGroupSize);
   }
 
   void Print(const Writer& aWriter, LocationService* aLocService,
              uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
@@ -1691,20 +1672,16 @@ Init(const malloc_table_t* aMallocTable)
   DMD_CREATE_TLS_INDEX(gTlsIndex);
 
   gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
   gStackTraceTable->init(8192);
 
   gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>();
   gLiveBlockTable->init(8192);
 
-  gDoubleReportBlockGroupTable =
-    InfallibleAllocPolicy::new_<DoubleReportBlockGroupTable>();
-  gDoubleReportBlockGroupTable->init(0);
-
   if (gMode == Test) {
     // OpenTestOrStressFile() can allocate.  So do this before setting
     // gIsDMDRunning so those allocations don't show up in our results.  Once
     // gIsDMDRunning is set we are intercepting malloc et al. in earnest.
     FILE* fp = OpenTestOrStressFile("test.dmd");
     gIsDMDRunning = true;
 
     StatusMsg("running test mode...\n");
@@ -1727,58 +1704,30 @@ Init(const malloc_table_t* aMallocTable)
 
   gIsDMDRunning = true;
 }
 
 //---------------------------------------------------------------------------
 // DMD reporting and unreporting
 //---------------------------------------------------------------------------
 
-void
-LiveBlock::Report(Thread* aT, bool aOnAlloc) const
-{
-  if (IsReported()) {
-    DoubleReportBlockKey key(mAllocStackTrace, mReportStackTrace,
-                             StackTrace::Get(aT));
-    DoubleReportBlockGroupTable::AddPtr p =
-      gDoubleReportBlockGroupTable->lookupForAdd(key);
-    if (!p) {
-      DoubleReportBlockGroup bg(key);
-      (void)gDoubleReportBlockGroupTable->add(p, bg);
-    }
-    p->Add(*this);
-
-  } else {
-    mReportStackTrace = StackTrace::Get(aT);
-    mReportedOnAlloc  = aOnAlloc;
-  }
-}
-
-void
-LiveBlock::UnreportIfNotReportedOnAlloc() const
-{
-  if (!mReportedOnAlloc) {
-    mReportStackTrace = nullptr;
-  }
-}
-
 static void
-ReportHelper(const void* aPtr, bool aOnAlloc)
+ReportHelper(const void* aPtr, bool aReportedOnAlloc)
 {
   if (!gIsDMDRunning || !aPtr) {
     return;
   }
 
   Thread* t = Thread::Fetch();
 
   AutoBlockIntercepts block(t);
   AutoLockState lock;
 
   if (LiveBlockTable::Ptr p = gLiveBlockTable->lookup(aPtr)) {
-    p->Report(t, aOnAlloc);
+    p->Report(t, aReportedOnAlloc);
   } 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.
   }
 }
 
@@ -1793,17 +1742,17 @@ ReportOnAlloc(const void* aPtr)
 {
   ReportHelper(aPtr, /* onAlloc */ true);
 }
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
-// This works for LiveBlockGroups, DoubleReportBlockGroups and FrameGroups.
+// This works for BlockGroups and FrameGroups.
 template <class TGroup>
 static void
 PrintSortedGroups(const Writer& aWriter, LocationService* aLocService,
                   const char* aStr, const char* astr,
                   const js::HashSet<TGroup, TGroup, InfallibleAllocPolicy>& aTGroupTable,
                   size_t aCategoryUsableSize, size_t aTotalUsableSize)
 {
   const char* name = TGroup::kName;
@@ -1851,35 +1800,35 @@ PrintSortedGroups(const Writer& aWriter,
   MOZ_ASSERT(aCategoryUsableSize == kNoSize ||
              aCategoryUsableSize == cumulativeUsableSize);
 }
 
 static void
 PrintSortedBlockAndFrameGroups(const Writer& aWriter,
                                LocationService* aLocService,
                                const char* aStr, const char* astr,
-                               const LiveBlockGroupTable& aLiveBlockGroupTable,
+                               const BlockGroupTable& aBlockGroupTable,
                                size_t aCategoryUsableSize,
                                size_t aTotalUsableSize)
 {
-  PrintSortedGroups(aWriter, aLocService, aStr, astr, aLiveBlockGroupTable,
+  PrintSortedGroups(aWriter, aLocService, aStr, astr, aBlockGroupTable,
                     aCategoryUsableSize, aTotalUsableSize);
 
   // Frame groups are totally dependent on vagaries of stack traces, so we
   // can't show them in test mode.
   if (gMode == Test) {
     return;
   }
 
   FrameGroupTable frameGroupTable;
   (void)frameGroupTable.init(2048);
-  for (LiveBlockGroupTable::Range r = aLiveBlockGroupTable.all();
+  for (BlockGroupTable::Range r = aBlockGroupTable.all();
        !r.empty();
        r.popFront()) {
-    const LiveBlockGroup& bg = r.front();
+    const BlockGroup& bg = r.front();
     const StackTrace* st = bg.mAllocStackTrace;
 
     // A single PC can appear multiple times in a stack trace.  We ignore
     // duplicates by first sorting and then ignoring adjacent duplicates.
     StackTrace sorted(*st);
     sorted.Sort();              // sorts the copy, not the original
     void* prevPc = (void*)intptr_t(-1);
     for (uint32_t i = 0; i < sorted.Length(); i++) {
@@ -1920,35 +1869,28 @@ SizeOf(Sizes* aSizes)
     StackTrace* const& st = r.front();
     aSizes->mStackTraces += MallocSizeOf(st);
   }
 
   aSizes->mStackTraceTable =
     gStackTraceTable->sizeOfIncludingThis(MallocSizeOf);
 
   aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
-
-  aSizes->mDoubleReportTable =
-    gDoubleReportBlockGroupTable->sizeOfIncludingThis(MallocSizeOf);
 }
 
 static void
 ClearGlobalState()
 {
   // Unreport all blocks, except those that were reported on allocation,
   // because they need to keep their reported marking.
   for (LiveBlockTable::Range r = gLiveBlockTable->all();
        !r.empty();
        r.popFront()) {
     r.front().UnreportIfNotReportedOnAlloc();
   }
-
-  // Clear errors.
-  gDoubleReportBlockGroupTable->finish();
-  (void)gDoubleReportBlockGroupTable->init();
 }
 
 MOZ_EXPORT void
 Dump(Writer aWriter)
 {
   if (!gIsDMDRunning) {
     const char* msg = "cannot Dump();  DMD was not enabled at startup\n";
     StatusMsg("%s", msg);
@@ -1957,130 +1899,146 @@ Dump(Writer aWriter)
   }
 
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
 
   static int dumpCount = 1;
   StatusMsg("Dump %d {\n", dumpCount++);
 
-  StatusMsg("  gathering live block groups...\n");
-
-  LiveBlockGroupTable unreportedLiveBlockGroupTable;
-  (void)unreportedLiveBlockGroupTable.init(1024);
+  StatusMsg("  gathering block groups...\n");
+
+  BlockGroupTable unreportedBlockGroupTable;
+  (void)unreportedBlockGroupTable.init(1024);
   size_t unreportedUsableSize = 0;
 
-  LiveBlockGroupTable reportedLiveBlockGroupTable;
-  (void)reportedLiveBlockGroupTable.init(1024);
-  size_t reportedUsableSize = 0;
+  BlockGroupTable onceReportedBlockGroupTable;
+  (void)onceReportedBlockGroupTable.init(1024);
+  size_t onceReportedUsableSize = 0;
+
+  BlockGroupTable twiceReportedBlockGroupTable;
+  (void)twiceReportedBlockGroupTable.init(0);
+  size_t twiceReportedUsableSize = 0;
 
   bool anyBlocksSampled = false;
 
   for (LiveBlockTable::Range r = gLiveBlockTable->all();
        !r.empty();
        r.popFront()) {
     const LiveBlock& b = r.front();
 
-    size_t& size = !b.IsReported() ? unreportedUsableSize : reportedUsableSize;
-    size += b.UsableSize();
-
-    LiveBlockGroupTable& table = !b.IsReported()
-                               ? unreportedLiveBlockGroupTable
-                               : reportedLiveBlockGroupTable;
-    LiveBlockKey liveKey(b);
-    LiveBlockGroupTable::AddPtr p = table.lookupForAdd(liveKey);
+    BlockGroupTable* table;
+    uint32_t numReports = b.NumReports();
+    if (numReports == 0) {
+      unreportedUsableSize += b.UsableSize();
+      table = &unreportedBlockGroupTable;
+    } else if (numReports == 1) {
+      onceReportedUsableSize += b.UsableSize();
+      table = &onceReportedBlockGroupTable;
+    } else {
+      MOZ_ASSERT(numReports == 2);
+      twiceReportedUsableSize += b.UsableSize();
+      table = &twiceReportedBlockGroupTable;
+    }
+    BlockGroupKey key(b);
+    BlockGroupTable::AddPtr p = table->lookupForAdd(key);
     if (!p) {
-      LiveBlockGroup bg(b);
-      (void)table.add(p, bg);
+      BlockGroup bg(b);
+      (void)table->add(p, bg);
     }
     p->Add(b);
 
     anyBlocksSampled = anyBlocksSampled || b.IsSampled();
   }
-  size_t totalUsableSize = unreportedUsableSize + reportedUsableSize;
+  size_t totalUsableSize =
+    unreportedUsableSize + onceReportedUsableSize + twiceReportedUsableSize;
 
   WriteTitle("Invocation\n");
   W("$DMD = '%s'\n", gDMDEnvVar);
   W("Sample-below size = %lld\n\n", (long long)(gSampleBelowSize));
 
   // Allocate this on the heap instead of the stack because it's fairly large.
   LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
 
-  PrintSortedGroups(aWriter, locService, "Double-reported", "double-reported",
-                    *gDoubleReportBlockGroupTable, kNoSize, kNoSize);
+  PrintSortedGroups(aWriter, locService, "Twice-reported", "twice-reported",
+                    twiceReportedBlockGroupTable, twiceReportedUsableSize,
+                    totalUsableSize);
 
   PrintSortedBlockAndFrameGroups(aWriter, locService,
                                  "Unreported", "unreported",
-                                 unreportedLiveBlockGroupTable,
+                                 unreportedBlockGroupTable,
                                  unreportedUsableSize, totalUsableSize);
 
   PrintSortedBlockAndFrameGroups(aWriter, locService,
-                                 "Reported", "reported",
-                                 reportedLiveBlockGroupTable,
-                                 reportedUsableSize, totalUsableSize);
+                                 "Once-reported", "once-reported",
+                                 onceReportedBlockGroupTable,
+                                 onceReportedUsableSize, totalUsableSize);
 
   bool showTilde = anyBlocksSampled;
   WriteTitle("Summary\n");
-  W("Total:      %s bytes\n",
+  W("Total:           %10s bytes\n",
     Show(totalUsableSize, gBuf1, kBufLen, showTilde));
-  W("Reported:   %s bytes (%5.2f%%)\n",
-    Show(reportedUsableSize, gBuf1, kBufLen, showTilde),
-    Percent(reportedUsableSize, totalUsableSize));
-  W("Unreported: %s bytes (%5.2f%%)\n",
+  W("Unreported:      %10s bytes (%5.2f%%)\n",
     Show(unreportedUsableSize, gBuf1, kBufLen, showTilde),
     Percent(unreportedUsableSize, totalUsableSize));
+  W("Once-reported:   %10s bytes (%5.2f%%)\n",
+    Show(onceReportedUsableSize, gBuf1, kBufLen, showTilde),
+    Percent(onceReportedUsableSize, totalUsableSize));
+  W("Twice-reported:  %10s bytes (%5.2f%%)\n",
+    Show(twiceReportedUsableSize, gBuf1, kBufLen, showTilde),
+    Percent(twiceReportedUsableSize, totalUsableSize));
 
   W("\n");
 
   // Stats are non-deterministic, so don't show them in test mode.
   if (gMode != Test) {
     Sizes sizes;
     SizeOf(&sizes);
 
     WriteTitle("Execution measurements\n");
 
     W("Data structures that persist after Dump() ends:\n");
 
-    W("  Stack traces:        %10s bytes\n",
+    W("  Stack traces:         %10s bytes\n",
       Show(sizes.mStackTraces, gBuf1, kBufLen));
 
-    W("  Stack trace table:   %10s bytes (%s entries, %s used)\n",
+    W("  Stack trace table:    %10s bytes (%s entries, %s used)\n",
       Show(sizes.mStackTraceTable,       gBuf1, kBufLen),
       Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
       Show(gStackTraceTable->count(),    gBuf3, kBufLen));
 
-    W("  Live block table:    %10s bytes (%s entries, %s used)\n",
+    W("  Live block table:     %10s bytes (%s entries, %s used)\n",
       Show(sizes.mLiveBlockTable,       gBuf1, kBufLen),
       Show(gLiveBlockTable->capacity(), gBuf2, kBufLen),
       Show(gLiveBlockTable->count(),    gBuf3, kBufLen));
 
-    W("\nData structures that are partly cleared after Dump() ends:\n");
-
-    W("  Double-report table: %10s bytes (%s entries, %s used)\n",
-      Show(sizes.mDoubleReportTable,                 gBuf1, kBufLen),
-      Show(gDoubleReportBlockGroupTable->capacity(), gBuf2, kBufLen),
-      Show(gDoubleReportBlockGroupTable->count(),    gBuf3, kBufLen));
-
     W("\nData structures that are destroyed after Dump() ends:\n");
 
     size_t unreportedSize =
-      unreportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
-    W("  Unreported table:    %10s bytes (%s entries, %s used)\n",
-      Show(unreportedSize,                           gBuf1, kBufLen),
-      Show(unreportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
-      Show(unreportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
-
-    size_t reportedSize =
-      reportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
-    W("  Reported table:      %10s bytes (%s entries, %s used)\n",
-      Show(reportedSize,                           gBuf1, kBufLen),
-      Show(reportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
-      Show(reportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
-
-    W("  Location service:    %10s bytes\n",
+      unreportedBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
+    W("  Unreported table:     %10s bytes (%s entries, %s used)\n",
+      Show(unreportedSize,                       gBuf1, kBufLen),
+      Show(unreportedBlockGroupTable.capacity(), gBuf2, kBufLen),
+      Show(unreportedBlockGroupTable.count(),    gBuf3, kBufLen));
+
+    size_t onceReportedSize =
+      onceReportedBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
+    W("  Once-reported table:  %10s bytes (%s entries, %s used)\n",
+      Show(onceReportedSize,                       gBuf1, kBufLen),
+      Show(onceReportedBlockGroupTable.capacity(), gBuf2, kBufLen),
+      Show(onceReportedBlockGroupTable.count(),    gBuf3, kBufLen));
+
+    size_t twiceReportedSize =
+      twiceReportedBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
+    W("  Twice-reported table: %10s bytes (%s entries, %s used)\n",
+      Show(twiceReportedSize,                       gBuf1, kBufLen),
+      Show(twiceReportedBlockGroupTable.capacity(), gBuf2, kBufLen),
+      Show(twiceReportedBlockGroupTable.count(),    gBuf3, kBufLen));
+
+    W("  Location service:     %10s bytes\n",
       Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen));
 
     W("\nCounts:\n");
 
     size_t hits   = locService->NumCacheHits();
     size_t misses = locService->NumCacheMisses();
     size_t requests = hits + misses;
     W("  Location service:    %10s requests\n",
@@ -2153,34 +2111,34 @@ RunTestMode(FILE* fp)
   for (i = 0; i < 10; i++) {
       a = (char*) malloc(100);
       UseItOrLoseIt(a);
   }
   free(a);
 
   // Min-sized block.
   // 1st Dump: reported.
-  // 2nd Dump: re-reported, twice;  double-report warning.
+  // 2nd Dump: thrice-reported.
   char* a2 = (char*) malloc(0);
   Report(a2);
 
   // Operator new[].
   // 1st Dump: reported.
   // 2nd Dump: reportedness carries over, due to ReportOnAlloc.
   char* b = new char[10];
   ReportOnAlloc(b);
 
   // ReportOnAlloc, then freed.
   // 1st Dump: freed, irrelevant.
   // 2nd Dump: freed, irrelevant.
   char* b2 = new char;
   ReportOnAlloc(b2);
   free(b2);
 
-  // 1st Dump: reported, plus 3 double-report warnings.
+  // 1st Dump: reported 4 times.
   // 2nd Dump: freed, irrelevant.
   char* c = (char*) calloc(10, 3);
   Report(c);
   for (int i = 0; i < 3; i++) {
     Report(c);
   }
 
   // 1st Dump: ignored.
@@ -2218,16 +2176,34 @@ RunTestMode(FILE* fp)
   // 2nd Dump: irrelevant.
   Report((void*)(intptr_t)0x0);
 
   // 1st Dump: mixture of reported and unreported.
   // 2nd Dump: all unreported.
   foo();
   foo();
 
+  // 1st Dump: twice-reported.
+  // 2nd Dump: twice-reported.
+  char* g1 = (char*) malloc(77);
+  ReportOnAlloc(g1);
+  ReportOnAlloc(g1);
+
+  // 1st Dump: twice-reported.
+  // 2nd Dump: once-reported.
+  char* g2 = (char*) malloc(78);
+  Report(g2);
+  ReportOnAlloc(g2);
+
+  // 1st Dump: twice-reported.
+  // 2nd Dump: once-reported.
+  char* g3 = (char*) malloc(79);
+  ReportOnAlloc(g3);
+  Report(g3);
+
   // All the odd-ball ones.
   // 1st Dump: all unreported.
   // 2nd Dump: all freed, irrelevant.
   // XXX: no memalign on Mac
 //void* x = memalign(64, 65);           // rounds up to 128
 //UseItOrLoseIt(x);
   // XXX: posix_memalign doesn't work on B2G, apparently
 //void* y;
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -50,17 +50,16 @@ Dump(Writer aWriter);
 MOZ_EXPORT void
 FpWrite(void* aFp, const char* aFmt, va_list aAp);
 
 struct Sizes
 {
   size_t mStackTraces;
   size_t mStackTraceTable;
   size_t mLiveBlockTable;
-  size_t mDoubleReportTable;
 
   Sizes() { Clear(); }
   void Clear() { memset(this, 0, sizeof(Sizes)); }
 };
 
 // Gets the size of various data structures.  Used to implement a memory
 // reporter for DMD.
 MOZ_EXPORT void
--- a/memory/replace/dmd/test-expected.dmd
+++ b/memory/replace/dmd/test-expected.dmd
@@ -1,303 +1,356 @@
 ------------------------------------------------------------------
 Invocation
 ------------------------------------------------------------------
 
 $DMD = '--mode=test'
 Sample-below size = 1
 
 ------------------------------------------------------------------
-Double-reported blocks
+Twice-reported blocks
 ------------------------------------------------------------------
 
 (none)
 
 ------------------------------------------------------------------
 Unreported blocks
 ------------------------------------------------------------------
 
 (none)
 
 ------------------------------------------------------------------
-Reported blocks
+Once-reported blocks
 ------------------------------------------------------------------
 
 (none)
 
 ------------------------------------------------------------------
 Summary
 ------------------------------------------------------------------
 
-Total:      0 bytes
-Reported:   0 bytes ( 0.00%)
-Unreported: 0 bytes ( 0.00%)
+Total:                    0 bytes
+Unreported:               0 bytes ( 0.00%)
+Once-reported:            0 bytes ( 0.00%)
+Twice-reported:           0 bytes ( 0.00%)
 
 ------------------------------------------------------------------
 Invocation
 ------------------------------------------------------------------
 
 $DMD = '--mode=test'
 Sample-below size = 1
 
 ------------------------------------------------------------------
-Double-reported blocks
+Twice-reported blocks
 ------------------------------------------------------------------
 
-Double-reported: 3 blocks in block group 1 of 1
- 96 bytes (90 requested / 6 slop)
+Twice-reported: 1 block in block group 1 of 4
+ 80 bytes (79 requested / 1 slop)
+ 0.53% of the heap (0.53% cumulative);  29.41% of twice-reported (29.41% cumulative)
+ Allocated at
+   (stack omitted due to test mode)
+
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
+   (stack omitted due to test mode)
+
+Twice-reported: 1 block in block group 2 of 4
+ 80 bytes (78 requested / 2 slop)
+ 0.53% of the heap (1.05% cumulative);  29.41% of twice-reported (58.82% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
- Previously reported at
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
+   (stack omitted due to test mode)
+
+Twice-reported: 1 block in block group 3 of 4
+ 80 bytes (77 requested / 3 slop)
+ 0.53% of the heap (1.58% cumulative);  29.41% of twice-reported (88.24% cumulative)
+ Allocated at
    (stack omitted due to test mode)
 
- Now reported at
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
+   (stack omitted due to test mode)
+
+Twice-reported: 1 block in block group 4 of 4
+ 32 bytes (30 requested / 2 slop)
+ 0.21% of the heap (1.79% cumulative);  11.76% of twice-reported (100.00% cumulative)
+ Allocated at
+   (stack omitted due to test mode)
+
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
 Unreported blocks
 ------------------------------------------------------------------
 
 Unreported: 1 block in block group 1 of 4
  4,096 bytes (1 requested / 4,095 slop)
- 27.44% of the heap (27.44% cumulative);  76.88% of unreported (76.88% cumulative)
+ 27.00% of the heap (27.00% cumulative);  76.88% of unreported (76.88% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 Unreported: 9 blocks in block group 2 of 4
  1,008 bytes (900 requested / 108 slop)
- 6.75% of the heap (34.19% cumulative);  18.92% of unreported (95.80% cumulative)
+ 6.65% of the heap (33.65% cumulative);  18.92% of unreported (95.80% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 Unreported: 2 blocks in block group 3 of 4
  112 bytes (112 requested / 0 slop)
- 0.75% of the heap (34.94% cumulative);  2.10% of unreported (97.90% cumulative)
+ 0.74% of the heap (34.39% cumulative);  2.10% of unreported (97.90% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 Unreported: 2 blocks in block group 4 of 4
  112 bytes (112 requested / 0 slop)
- 0.75% of the heap (35.69% cumulative);  2.10% of unreported (100.00% cumulative)
+ 0.74% of the heap (35.13% cumulative);  2.10% of unreported (100.00% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
-Reported blocks
+Once-reported blocks
 ------------------------------------------------------------------
 
-Reported: 1 block in block group 1 of 12
+Once-reported: 1 block in block group 1 of 11
  8,192 bytes (4,097 requested / 4,095 slop)
- 54.88% of the heap (54.88% cumulative);  85.33% of reported (85.33% cumulative)
+ 54.01% of the heap (54.01% cumulative);  85.62% of once-reported (85.62% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 2 of 12
+Once-reported: 1 block in block group 2 of 11
  512 bytes (512 requested / 0 slop)
- 3.43% of the heap (58.31% cumulative);  5.33% of reported (90.67% cumulative)
+ 3.38% of the heap (57.38% cumulative);  5.35% of once-reported (90.97% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 2 blocks in block group 3 of 12
+Once-reported: 2 blocks in block group 3 of 11
  240 bytes (240 requested / 0 slop)
- 1.61% of the heap (59.91% cumulative);  2.50% of reported (93.17% cumulative)
- Allocated at
-   (stack omitted due to test mode)
-
- Reported at
-   (stack omitted due to test mode)
-
-Reported: 2 blocks in block group 4 of 12
- 240 bytes (240 requested / 0 slop)
- 1.61% of the heap (61.52% cumulative);  2.50% of reported (95.67% cumulative)
+ 1.58% of the heap (58.97% cumulative);  2.51% of once-reported (93.48% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 5 of 12
- 96 bytes (96 requested / 0 slop)
- 0.64% of the heap (62.17% cumulative);  1.00% of reported (96.67% cumulative)
+Once-reported: 2 blocks in block group 4 of 11
+ 240 bytes (240 requested / 0 slop)
+ 1.58% of the heap (60.55% cumulative);  2.51% of once-reported (95.99% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 6 of 12
+Once-reported: 1 block in block group 5 of 11
  96 bytes (96 requested / 0 slop)
- 0.64% of the heap (62.81% cumulative);  1.00% of reported (97.67% cumulative)
+ 0.63% of the heap (61.18% cumulative);  1.00% of once-reported (96.99% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 7 of 12
- 80 bytes (80 requested / 0 slop)
- 0.54% of the heap (63.34% cumulative);  0.83% of reported (98.50% cumulative)
+Once-reported: 1 block in block group 6 of 11
+ 96 bytes (96 requested / 0 slop)
+ 0.63% of the heap (61.81% cumulative);  1.00% of once-reported (97.99% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 8 of 12
+Once-reported: 1 block in block group 7 of 11
  80 bytes (80 requested / 0 slop)
- 0.54% of the heap (63.88% cumulative);  0.83% of reported (99.33% cumulative)
+ 0.53% of the heap (62.34% cumulative);  0.84% of once-reported (98.83% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 9 of 12
- 32 bytes (30 requested / 2 slop)
- 0.21% of the heap (64.09% cumulative);  0.33% of reported (99.67% cumulative)
+Once-reported: 1 block in block group 8 of 11
+ 80 bytes (80 requested / 0 slop)
+ 0.53% of the heap (62.87% cumulative);  0.84% of once-reported (99.67% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 10 of 12
+Once-reported: 1 block in block group 9 of 11
  16 bytes (10 requested / 6 slop)
- 0.11% of the heap (64.20% cumulative);  0.17% of reported (99.83% cumulative)
+ 0.11% of the heap (62.97% cumulative);  0.17% of once-reported (99.83% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 11 of 12
+Once-reported: 1 block in block group 10 of 11
  8 bytes (0 requested / 8 slop)
- 0.05% of the heap (64.26% cumulative);  0.08% of reported (99.92% cumulative)
+ 0.05% of the heap (63.03% cumulative);  0.08% of once-reported (99.92% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 12 of 12
+Once-reported: 1 block in block group 11 of 11
  8 bytes (0 requested / 8 slop)
- 0.05% of the heap (64.31% cumulative);  0.08% of reported (100.00% cumulative)
+ 0.05% of the heap (63.08% cumulative);  0.08% of once-reported (100.00% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
 Summary
 ------------------------------------------------------------------
 
-Total:      14,928 bytes
-Reported:   9,600 bytes (64.31%)
-Unreported: 5,328 bytes (35.69%)
+Total:               15,168 bytes
+Unreported:           5,328 bytes (35.13%)
+Once-reported:        9,568 bytes (63.08%)
+Twice-reported:         272 bytes ( 1.79%)
 
 ------------------------------------------------------------------
 Invocation
 ------------------------------------------------------------------
 
 $DMD = '--mode=test'
 Sample-below size = 1
 
 ------------------------------------------------------------------
-Double-reported blocks
+Twice-reported blocks
 ------------------------------------------------------------------
 
-Double-reported: 1 block in block group 1 of 1
- 8 bytes (0 requested / 8 slop)
+Twice-reported: 1 block in block group 1 of 2
+ 80 bytes (77 requested / 3 slop)
+ 2.82% of the heap (2.82% cumulative);  90.91% of twice-reported (90.91% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
- Previously reported at
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
    (stack omitted due to test mode)
 
- Now reported at
+Twice-reported: 1 block in block group 2 of 2
+ 8 bytes (0 requested / 8 slop)
+ 0.28% of the heap (3.10% cumulative);  9.09% of twice-reported (100.00% cumulative)
+ Allocated at
+   (stack omitted due to test mode)
+
+ Reported at
+   (stack omitted due to test mode)
+
+ Reported again at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
 Unreported blocks
 ------------------------------------------------------------------
 
 Unreported: 9 blocks in block group 1 of 3
  1,008 bytes (900 requested / 108 slop)
- 38.77% of the heap (38.77% cumulative);  48.84% of unreported (48.84% cumulative)
+ 35.49% of the heap (35.49% cumulative);  48.84% of unreported (48.84% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 Unreported: 6 blocks in block group 2 of 3
  528 bytes (528 requested / 0 slop)
- 20.31% of the heap (59.08% cumulative);  25.58% of unreported (74.42% cumulative)
+ 18.59% of the heap (54.08% cumulative);  25.58% of unreported (74.42% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 Unreported: 6 blocks in block group 3 of 3
  528 bytes (528 requested / 0 slop)
- 20.31% of the heap (79.38% cumulative);  25.58% of unreported (100.00% cumulative)
+ 18.59% of the heap (72.68% cumulative);  25.58% of unreported (100.00% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
-Reported blocks
+Once-reported blocks
 ------------------------------------------------------------------
 
-Reported: 1 block in block group 1 of 3
+Once-reported: 1 block in block group 1 of 4
  512 bytes (512 requested / 0 slop)
- 19.69% of the heap (19.69% cumulative);  95.52% of reported (95.52% cumulative)
+ 18.03% of the heap (18.03% cumulative);  74.42% of once-reported (74.42% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 2 of 3
- 16 bytes (10 requested / 6 slop)
- 0.62% of the heap (20.31% cumulative);  2.99% of reported (98.51% cumulative)
+Once-reported: 1 block in block group 2 of 4
+ 80 bytes (79 requested / 1 slop)
+ 2.82% of the heap (20.85% cumulative);  11.63% of once-reported (86.05% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
-Reported: 1 block in block group 3 of 3
- 8 bytes (0 requested / 8 slop)
- 0.31% of the heap (20.62% cumulative);  1.49% of reported (100.00% cumulative)
+Once-reported: 1 block in block group 3 of 4
+ 80 bytes (78 requested / 2 slop)
+ 2.82% of the heap (23.66% cumulative);  11.63% of once-reported (97.67% cumulative)
+ Allocated at
+   (stack omitted due to test mode)
+
+ Reported at
+   (stack omitted due to test mode)
+
+Once-reported: 1 block in block group 4 of 4
+ 16 bytes (10 requested / 6 slop)
+ 0.56% of the heap (24.23% cumulative);  2.33% of once-reported (100.00% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
  Reported at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
 Summary
 ------------------------------------------------------------------
 
-Total:      2,600 bytes
-Reported:   536 bytes (20.62%)
-Unreported: 2,064 bytes (79.38%)
+Total:                2,840 bytes
+Unreported:           2,064 bytes (72.68%)
+Once-reported:          688 bytes (24.23%)
+Twice-reported:          88 bytes ( 3.10%)
 
 ------------------------------------------------------------------
 Invocation
 ------------------------------------------------------------------
 
 $DMD = '--mode=test'
 Sample-below size = 128
 
 ------------------------------------------------------------------
-Double-reported blocks
+Twice-reported blocks
 ------------------------------------------------------------------
 
 (none)
 
 ------------------------------------------------------------------
 Unreported blocks
 ------------------------------------------------------------------
 
@@ -339,21 +392,22 @@ Unreported: ~1 block in block group 6 of
 
 Unreported: ~1 block in block group 7 of 7
  ~128 bytes (~128 requested / ~0 slop)
  8.99% of the heap (100.00% cumulative);  8.99% of unreported (100.00% cumulative)
  Allocated at
    (stack omitted due to test mode)
 
 ------------------------------------------------------------------
-Reported blocks
+Once-reported blocks
 ------------------------------------------------------------------
 
 (none)
 
 ------------------------------------------------------------------
 Summary
 ------------------------------------------------------------------
 
-Total:      ~1,424 bytes
-Reported:   ~0 bytes ( 0.00%)
-Unreported: ~1,424 bytes (100.00%)
+Total:               ~1,424 bytes
+Unreported:          ~1,424 bytes (100.00%)
+Once-reported:           ~0 bytes ( 0.00%)
+Twice-reported:          ~0 bytes ( 0.00%)
 
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -603,20 +603,16 @@ public:
     REPORT("explicit/dmd/stack-trace-table",
            sizes.mStackTraceTable,
            "Memory used by DMD's stack trace table.");
 
     REPORT("explicit/dmd/live-block-table",
            sizes.mLiveBlockTable,
            "Memory used by DMD's live block table.");
 
-    REPORT("explicit/dmd/double-report-table",
-           sizes.mDoubleReportTable,
-           "Memory used by DMD's double-report table.");
-
 #undef REPORT
 
     return NS_OK;
   }
 
   NS_IMETHOD GetExplicitNonHeap(int64_t *n)
   {
     // No non-heap allocations.