summaryrefslogtreecommitdiffstats
path: root/xpcom/base/CountingAllocatorBase.h
blob: 68a405eaee2df797660a8975493434e353b2ee1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/* -*- 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/. */

#ifndef CountingAllocatorBase_h
#define CountingAllocatorBase_h

#include <cstdlib>
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/mozalloc.h"
#include "nsIMemoryReporter.h"

namespace mozilla {

// This CRTP class handles several details of wrapping allocators and should
// be preferred to manually counting with MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC
// and MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE.  The typical use is in a memory
// reporter for a particular third party library:
//
//   class MyMemoryReporter : public CountingAllocatorBase<MyMemoryReporter>
//   {
//     ...
//     NS_IMETHOD
//     CollectReports(nsIHandleReportCallback* aHandleReport,
//                    nsISupports* aData, bool aAnonymize) override
//     {
//        MOZ_COLLECT_REPORT(
//          "explicit/path/to/somewhere", KIND_HEAP, UNITS_BYTES,
//          MemoryAllocated(),
//          "A description of what we are reporting.");
//
//        return NS_OK;
//     }
//   };
//
//   ...somewhere later in the code...
//   SetThirdPartyMemoryFunctions(MyMemoryReporter::CountingAlloc,
//                                MyMemoryReporter::CountingFree);
template <typename T>
class CountingAllocatorBase {
 public:
  CountingAllocatorBase() {
#ifdef DEBUG
    // There must be only one instance of this class, due to |sAmount| being
    // static.
    static bool hasRun = false;
    MOZ_ASSERT(!hasRun);
    hasRun = true;
#endif
  }

  static size_t MemoryAllocated() { return sAmount; }

  static void* CountingMalloc(size_t size) {
    void* p = malloc(size);
    sAmount += MallocSizeOfOnAlloc(p);
    return p;
  }

  static void* CountingCalloc(size_t nmemb, size_t size) {
    void* p = calloc(nmemb, size);
    sAmount += MallocSizeOfOnAlloc(p);
    return p;
  }

  static void* CountingRealloc(void* p, size_t size) {
    size_t oldsize = MallocSizeOfOnFree(p);
    void* pnew = realloc(p, size);
    if (pnew) {
      size_t newsize = MallocSizeOfOnAlloc(pnew);
      sAmount += newsize - oldsize;
    } else if (size == 0) {
      // We asked for a 0-sized (re)allocation of some existing pointer
      // and received NULL in return.  0-sized allocations are permitted
      // to either return NULL or to allocate a unique object per call (!).
      // For a malloc implementation that chooses the second strategy,
      // that allocation may fail (unlikely, but possible).
      //
      // Given a NULL return value and an allocation size of 0, then, we
      // don't know if that means the original pointer was freed or if
      // the allocation of the unique object failed.  If the original
      // pointer was freed, then we have nothing to do here.  If the
      // allocation of the unique object failed, the original pointer is
      // still valid and we ought to undo the decrement from above.
      // However, we have no way of knowing how the underlying realloc
      // implementation is behaving.  Assuming that the original pointer
      // was freed is the safest course of action.  We do, however, need
      // to note that we freed memory.
      sAmount -= oldsize;
    } else {
      // realloc failed.  The amount allocated hasn't changed.
    }
    return pnew;
  }

  // Some library code expects that realloc(x, 0) will free x, which is not
  // the behavior of the version of jemalloc we're using, so this wrapped
  // version of realloc is needed.
  static void* CountingFreeingRealloc(void* p, size_t size) {
    if (size == 0) {
      CountingFree(p);
      return nullptr;
    }
    return CountingRealloc(p, size);
  }

  static void CountingFree(void* p) {
    sAmount -= MallocSizeOfOnFree(p);
    free(p);
  }

  // Infallible-allocation wrappers for the counting malloc/calloc/realloc
  // functions, for clients that don't safely handle allocation failures
  // themselves.
  static void* InfallibleCountingMalloc(size_t size) {
    void* p = moz_xmalloc(size);
    sAmount += MallocSizeOfOnAlloc(p);
    return p;
  }

  static void* InfallibleCountingCalloc(size_t nmemb, size_t size) {
    void* p = moz_xcalloc(nmemb, size);
    sAmount += MallocSizeOfOnAlloc(p);
    return p;
  }

  static void* InfallibleCountingRealloc(void* p, size_t size) {
    size_t oldsize = MallocSizeOfOnFree(p);
    void* pnew = moz_xrealloc(p, size);
    if (pnew) {
      size_t newsize = MallocSizeOfOnAlloc(pnew);
      sAmount += newsize - oldsize;
    } else if (size == 0) {
      // See comment in CountingRealloc above.
      sAmount -= oldsize;
    } else {
      // realloc failed.  The amount allocated hasn't changed.
    }
    return pnew;
  }

 private:
  // |sAmount| can be (implicitly) accessed by multiple threads, so it
  // must be thread-safe. It may be written during GC, so accesses are not
  // recorded.
  typedef Atomic<size_t, SequentiallyConsistent> AmountType;
  static AmountType sAmount;

  MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc)
  MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree)
};

}  // namespace mozilla

#endif  // CountingAllocatorBase_h