Bug 1910362 - Create new Nimbus helper r=aaronmt,ohorvath
[gecko.git] / xpcom / base / CodeAddressService.h
blob821bf2c73cf3740b3b0a27cffbef59c25fa82a5e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef CodeAddressService_h__
8 #define CodeAddressService_h__
10 #include <cstddef>
11 #include <cstdint>
12 #include <cstring>
13 #include "mozilla/AllocPolicy.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/HashFunctions.h"
16 #include "mozilla/HashTable.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/StackWalk.h"
20 namespace mozilla {
22 namespace detail {
24 template <class AllocPolicy>
25 class CodeAddressServiceAllocPolicy : public AllocPolicy {
26 public:
27 char* strdup_(const char* aStr) {
28 char* s = AllocPolicy::template pod_malloc<char>(strlen(aStr) + 1);
29 if (!s) {
30 MOZ_CRASH("CodeAddressService OOM");
32 strcpy(s, aStr);
33 return s;
37 // Default implementation of DescribeCodeAddressLock.
38 struct DefaultDescribeCodeAddressLock {
39 static void Unlock() {}
40 static void Lock() {}
41 // Because CodeAddressService asserts that IsLocked() is true, returning true
42 // here is a sensible default when there is no relevant lock.
43 static bool IsLocked() { return true; }
46 } // namespace detail
48 // This class is used to print details about code locations.
50 // |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h.
52 // |DescribeCodeAddressLock| is needed when the callers may be holding a lock
53 // used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement
54 // static methods IsLocked(), Unlock() and Lock().
55 template <class AllocPolicy_ = MallocAllocPolicy,
56 class DescribeCodeAddressLock =
57 detail::DefaultDescribeCodeAddressLock>
58 class CodeAddressService
59 : private detail::CodeAddressServiceAllocPolicy<AllocPolicy_> {
60 protected:
61 // GetLocation() is the key function in this class. It's basically a wrapper
62 // around MozDescribeCodeAddress.
64 // However, MozDescribeCodeAddress is very slow on some platforms, and we
65 // have lots of repeated (i.e. same PC) calls to it. So we do some caching
66 // of results. Each cached result includes two strings (|mFunction| and
67 // |mLibrary|), so we also optimize them for space in the following ways.
69 // - The number of distinct library names is small, e.g. a few dozen. There
70 // is lots of repetition, especially of libxul. So we intern them in their
71 // own table, which saves space over duplicating them for each cache entry.
73 // - The number of distinct function names is much higher, so we duplicate
74 // them in each cache entry. That's more space-efficient than interning
75 // because entries containing single-occurrence function names are quickly
76 // overwritten, and their copies released. In addition, empty function
77 // names are common, so we use nullptr to represent them compactly.
79 using AllocPolicy = detail::CodeAddressServiceAllocPolicy<AllocPolicy_>;
80 using StringHashSet = HashSet<const char*, CStringHasher, AllocPolicy>;
82 StringHashSet mLibraryStrings;
84 struct Entry : private AllocPolicy {
85 const void* mPc;
86 char* mFunction; // owned by the Entry; may be null
87 const char* mLibrary; // owned by mLibraryStrings; never null
88 // in a non-empty entry is in use
89 ptrdiff_t mLOffset;
90 char* mFileName; // owned by the Entry; may be null
91 uint32_t mLineNo : 31;
92 uint32_t mInUse : 1; // is the entry used?
94 Entry()
95 : mPc(0),
96 mFunction(nullptr),
97 mLibrary(nullptr),
98 mLOffset(0),
99 mFileName(nullptr),
100 mLineNo(0),
101 mInUse(0) {}
103 ~Entry() {
104 // We don't free mLibrary because it's externally owned.
105 AllocPolicy::free_(mFunction);
106 AllocPolicy::free_(mFileName);
109 void Replace(const void* aPc, const char* aFunction, const char* aLibrary,
110 ptrdiff_t aLOffset, const char* aFileName,
111 unsigned long aLineNo) {
112 mPc = aPc;
114 // Convert "" to nullptr. Otherwise, make a copy of the name.
115 AllocPolicy::free_(mFunction);
116 mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction);
117 AllocPolicy::free_(mFileName);
118 mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(aFileName);
120 mLibrary = aLibrary;
121 mLOffset = aLOffset;
122 mLineNo = aLineNo;
124 mInUse = 1;
127 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
128 // Don't measure mLibrary because it's externally owned.
129 size_t n = 0;
130 n += aMallocSizeOf(mFunction);
131 n += aMallocSizeOf(mFileName);
132 return n;
136 const char* InternLibraryString(const char* aString) {
137 auto p = mLibraryStrings.lookupForAdd(aString);
138 if (p) {
139 return *p;
142 const char* newString = AllocPolicy::strdup_(aString);
143 if (!mLibraryStrings.add(p, newString)) {
144 MOZ_CRASH("CodeAddressService OOM");
146 return newString;
149 Entry& GetEntry(const void* aPc) {
150 MOZ_ASSERT(DescribeCodeAddressLock::IsLocked());
152 uint32_t index = HashGeneric(aPc) & kMask;
153 MOZ_ASSERT(index < kNumEntries);
154 Entry& entry = mEntries[index];
156 if (!entry.mInUse || entry.mPc != aPc) {
157 mNumCacheMisses++;
159 // MozDescribeCodeAddress can (on Linux) acquire a lock inside
160 // the shared library loader. Another thread might call malloc
161 // while holding that lock (when loading a shared library). So
162 // we have to exit the lock around this call. For details, see
163 // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
164 MozCodeAddressDetails details;
166 DescribeCodeAddressLock::Unlock();
167 (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details);
168 DescribeCodeAddressLock::Lock();
171 const char* library = InternLibraryString(details.library);
172 entry.Replace(aPc, details.function, library, details.loffset,
173 details.filename, details.lineno);
175 } else {
176 mNumCacheHits++;
179 MOZ_ASSERT(entry.mPc == aPc);
181 return entry;
184 // A direct-mapped cache. When doing dmd::Analyze() just after starting
185 // desktop Firefox (which is similar to analyzing after a longer-running
186 // session, thanks to the limit on how many records we print), a cache with
187 // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit
188 // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB
189 // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms).
190 static const size_t kNumEntries = 1 << 12;
191 static const size_t kMask = kNumEntries - 1;
192 Entry mEntries[kNumEntries];
194 size_t mNumCacheHits;
195 size_t mNumCacheMisses;
197 public:
198 CodeAddressService()
199 : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {}
201 ~CodeAddressService() {
202 for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) {
203 AllocPolicy::free_(const_cast<char*>(iter.get()));
207 // Returns the minimum number of characters necessary to format the frame
208 // information, without the terminating null. The buffer will be truncated
209 // if the returned value is greater than aBufLen-1.
210 int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf,
211 size_t aBufLen) {
212 Entry& entry = GetEntry(aPc);
213 return MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc,
214 entry.mFunction, entry.mLibrary, entry.mLOffset,
215 entry.mFileName, entry.mLineNo);
218 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
219 size_t n = aMallocSizeOf(this);
220 for (uint32_t i = 0; i < kNumEntries; i++) {
221 n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf);
224 n += mLibraryStrings.shallowSizeOfExcludingThis(aMallocSizeOf);
225 for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) {
226 n += aMallocSizeOf(iter.get());
229 return n;
232 size_t CacheCapacity() const { return kNumEntries; }
234 size_t CacheCount() const {
235 size_t n = 0;
236 for (size_t i = 0; i < kNumEntries; i++) {
237 if (mEntries[i].mInUse) {
238 n++;
241 return n;
244 size_t NumCacheHits() const { return mNumCacheHits; }
245 size_t NumCacheMisses() const { return mNumCacheMisses; }
248 } // namespace mozilla
250 #endif // CodeAddressService_h__