| File: | root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp |
| Warning: | line 287, column 5 The first element of the 2nd argument is undefined |
| Note: | line 287, column 5 Other elements might also be undefined |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* This Source Code Form is subject to the terms of the Mozilla Public | |||
| 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this | |||
| 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |||
| 4 | ||||
| 5 | #define MOZ_MEMORY_IMPL | |||
| 6 | #include "mozmemory_wrap.h" | |||
| 7 | ||||
| 8 | #ifdef _WIN32 | |||
| 9 | # include <windows.h> | |||
| 10 | # include <io.h> | |||
| 11 | typedef intptr_t ssize_t; | |||
| 12 | #else | |||
| 13 | # include <sys/mman.h> | |||
| 14 | # include <unistd.h> | |||
| 15 | #endif | |||
| 16 | #ifdef XP_LINUX1 | |||
| 17 | # include <fcntl.h> | |||
| 18 | # include <stdlib.h> | |||
| 19 | #endif | |||
| 20 | #include <algorithm> | |||
| 21 | #include <cmath> | |||
| 22 | #include <cstdio> | |||
| 23 | #include <cstring> | |||
| 24 | ||||
| 25 | #include "mozilla/Assertions.h" | |||
| 26 | #include "mozilla/MathAlgorithms.h" | |||
| 27 | #include "mozilla/Maybe.h" | |||
| 28 | #include "FdPrintf.h" | |||
| 29 | ||||
| 30 | using namespace mozilla; | |||
| 31 | ||||
| 32 | static void die(const char* message) { | |||
| 33 | /* Here, it doesn't matter that fprintf may allocate memory. */ | |||
| 34 | fprintf(stderrstderr, "%s\n", message); | |||
| 35 | exit(1); | |||
| 36 | } | |||
| 37 | ||||
| 38 | #ifdef XP_LINUX1 | |||
| 39 | MOZ_RUNINIT static size_t sPageSize = []() { return sysconf(_SC_PAGESIZE_SC_PAGESIZE); }(); | |||
| 40 | #endif | |||
| 41 | ||||
| 42 | /* We don't want to be using malloc() to allocate our internal tracking | |||
| 43 | * data, because that would change the parameters of what is being measured, | |||
| 44 | * so we want to use data types that directly use mmap/VirtualAlloc. */ | |||
| 45 | template <typename T, size_t Len> | |||
| 46 | class MappedArray { | |||
| 47 | public: | |||
| 48 | MappedArray() : mPtr(nullptr) { | |||
| 49 | #ifdef XP_LINUX1 | |||
| 50 | MOZ_RELEASE_ASSERT(!((sizeof(T) * Len) & (sPageSize - 1)),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!((sizeof(T) * Len) & (sPageSize - 1)))>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(!((sizeof(T) * Len) & (sPageSize - 1))))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!((sizeof(T) * Len) & (sPageSize - 1))" " (" "MappedArray size must be a multiple of the page size" ")" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 51); do { } while (false); do { MOZ_CrashSequence(__null, 51 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) | |||
| 51 | "MappedArray size must be a multiple of the page size")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!((sizeof(T) * Len) & (sPageSize - 1)))>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(!((sizeof(T) * Len) & (sPageSize - 1))))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!((sizeof(T) * Len) & (sPageSize - 1))" " (" "MappedArray size must be a multiple of the page size" ")" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 51); do { } while (false); do { MOZ_CrashSequence(__null, 51 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 52 | #endif | |||
| 53 | } | |||
| 54 | ||||
| 55 | ~MappedArray() { | |||
| 56 | if (mPtr) { | |||
| 57 | #ifdef _WIN32 | |||
| 58 | VirtualFree(mPtr, sizeof(T) * Len, MEM_RELEASE); | |||
| 59 | #elif defined(XP_LINUX1) | |||
| 60 | munmap(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(mPtr) - | |||
| 61 | sPageSize), | |||
| 62 | sizeof(T) * Len + sPageSize * 2); | |||
| 63 | #else | |||
| 64 | munmap(mPtr, sizeof(T) * Len); | |||
| 65 | #endif | |||
| 66 | } | |||
| 67 | } | |||
| 68 | ||||
| 69 | T& operator[](size_t aIndex) const { | |||
| 70 | if (mPtr) { | |||
| 71 | return mPtr[aIndex]; | |||
| 72 | } | |||
| 73 | ||||
| 74 | #ifdef _WIN32 | |||
| 75 | mPtr = reinterpret_cast<T*>(VirtualAlloc( | |||
| 76 | nullptr, sizeof(T) * Len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); | |||
| 77 | if (mPtr == nullptr) { | |||
| 78 | die("VirtualAlloc error"); | |||
| 79 | } | |||
| 80 | #else | |||
| 81 | size_t data_size = sizeof(T) * Len; | |||
| 82 | size_t size = data_size; | |||
| 83 | # ifdef XP_LINUX1 | |||
| 84 | // See below | |||
| 85 | size += sPageSize * 2; | |||
| 86 | # endif | |||
| 87 | mPtr = reinterpret_cast<T*>(mmap(nullptr, size, PROT_READ0x1 | PROT_WRITE0x2, | |||
| 88 | MAP_ANON0x20 | MAP_PRIVATE0x02, -1, 0)); | |||
| 89 | if (mPtr == MAP_FAILED((void *) -1)) { | |||
| 90 | die("Mmap error"); | |||
| 91 | } | |||
| 92 | # ifdef XP_LINUX1 | |||
| 93 | // On Linux we request a page on either side of the allocation and | |||
| 94 | // mprotect them. This prevents mappings in /proc/self/smaps from being | |||
| 95 | // merged and allows us to parse this file to calculate the allocator's RSS. | |||
| 96 | MOZ_ASSERT(0 == mprotect(mPtr, sPageSize, 0))do { static_assert( mozilla::detail::AssertionConditionType< decltype(0 == mprotect(mPtr, sPageSize, 0))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(0 == mprotect(mPtr, sPageSize , 0)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("0 == mprotect(mPtr, sPageSize, 0)", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 96); do { } while (false); do { MOZ_CrashSequence(__null, 96 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 97 | MOZ_ASSERT(0 == mprotect(reinterpret_cast<void*>(do { static_assert( mozilla::detail::AssertionConditionType< decltype(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("0 == mprotect(reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0)" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 100); do { } while (false); do { MOZ_CrashSequence(__null, 100 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) | |||
| 98 | reinterpret_cast<uintptr_t>(mPtr) + data_size +do { static_assert( mozilla::detail::AssertionConditionType< decltype(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("0 == mprotect(reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0)" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 100); do { } while (false); do { MOZ_CrashSequence(__null, 100 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) | |||
| 99 | sPageSize),do { static_assert( mozilla::detail::AssertionConditionType< decltype(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("0 == mprotect(reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0)" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 100); do { } while (false); do { MOZ_CrashSequence(__null, 100 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) | |||
| 100 | sPageSize, 0))do { static_assert( mozilla::detail::AssertionConditionType< decltype(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(0 == mprotect(reinterpret_cast<void*>( reinterpret_cast <uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("0 == mprotect(reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(mPtr) + data_size + sPageSize), sPageSize, 0)" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 100); do { } while (false); do { MOZ_CrashSequence(__null, 100 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 101 | mPtr = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(mPtr) + sPageSize); | |||
| 102 | # endif | |||
| 103 | #endif | |||
| 104 | return mPtr[aIndex]; | |||
| 105 | } | |||
| 106 | ||||
| 107 | bool ownsMapping(uintptr_t addr) const { return addr == (uintptr_t)mPtr; } | |||
| 108 | ||||
| 109 | bool allocated() const { return !!mPtr; } | |||
| 110 | ||||
| 111 | private: | |||
| 112 | mutable T* mPtr; | |||
| 113 | }; | |||
| 114 | ||||
| 115 | /* Type for records of allocations. */ | |||
| 116 | struct MemSlot { | |||
| 117 | void* mPtr; | |||
| 118 | ||||
| 119 | // mRequest is only valid if mPtr is non-null. It doesn't need to be cleared | |||
| 120 | // when memory is freed or realloc()ed. | |||
| 121 | size_t mRequest; | |||
| 122 | }; | |||
| 123 | ||||
| 124 | /* An almost infinite list of slots. | |||
| 125 | * In essence, this is a linked list of arrays of groups of slots. | |||
| 126 | * Each group is 1MB. On 64-bits, one group allows to store 64k allocations. | |||
| 127 | * Each MemSlotList instance can store 1023 such groups, which means more | |||
| 128 | * than 67M allocations. In case more would be needed, we chain to another | |||
| 129 | * MemSlotList, and so on. | |||
| 130 | * Using 1023 groups makes the MemSlotList itself page sized on 32-bits | |||
| 131 | * and 2 pages-sized on 64-bits. | |||
| 132 | */ | |||
| 133 | class MemSlotList { | |||
| 134 | static constexpr size_t kGroups = 1024 - 1; | |||
| 135 | static constexpr size_t kGroupSize = (1024 * 1024) / sizeof(MemSlot); | |||
| 136 | ||||
| 137 | MappedArray<MemSlot, kGroupSize> mSlots[kGroups]; | |||
| 138 | MappedArray<MemSlotList, 1> mNext; | |||
| 139 | ||||
| 140 | public: | |||
| 141 | MemSlot& operator[](size_t aIndex) const { | |||
| 142 | if (aIndex < kGroupSize * kGroups) { | |||
| 143 | return mSlots[aIndex / kGroupSize][aIndex % kGroupSize]; | |||
| 144 | } | |||
| 145 | aIndex -= kGroupSize * kGroups; | |||
| 146 | return mNext[0][aIndex]; | |||
| 147 | } | |||
| 148 | ||||
| 149 | // Ask if any of the memory-mapped buffers use this range. | |||
| 150 | bool ownsMapping(uintptr_t aStart) const { | |||
| 151 | for (const auto& slot : mSlots) { | |||
| 152 | if (slot.allocated() && slot.ownsMapping(aStart)) { | |||
| 153 | return true; | |||
| 154 | } | |||
| 155 | } | |||
| 156 | return mNext.ownsMapping(aStart) || | |||
| 157 | (mNext.allocated() && mNext[0].ownsMapping(aStart)); | |||
| 158 | } | |||
| 159 | }; | |||
| 160 | ||||
| 161 | /* Helper class for memory buffers */ | |||
| 162 | class Buffer { | |||
| 163 | public: | |||
| 164 | Buffer() : mBuf(nullptr), mLength(0) {} | |||
| 165 | ||||
| 166 | Buffer(const void* aBuf, size_t aLength) | |||
| 167 | : mBuf(reinterpret_cast<const char*>(aBuf)), mLength(aLength) {} | |||
| 168 | ||||
| 169 | /* Constructor for string literals. */ | |||
| 170 | template <size_t Size> | |||
| 171 | explicit Buffer(const char (&aStr)[Size]) : mBuf(aStr), mLength(Size - 1) {} | |||
| 172 | ||||
| 173 | /* Returns a sub-buffer up-to but not including the given aNeedle character. | |||
| 174 | * The "parent" buffer itself is altered to begin after the aNeedle | |||
| 175 | * character. | |||
| 176 | * If the aNeedle character is not found, return the entire buffer, and empty | |||
| 177 | * the "parent" buffer. */ | |||
| 178 | Buffer SplitChar(char aNeedle) { | |||
| 179 | char* buf = const_cast<char*>(mBuf); | |||
| 180 | char* c = reinterpret_cast<char*>(memchr(buf, aNeedle, mLength)); | |||
| 181 | if (!c) { | |||
| 182 | return Split(mLength); | |||
| 183 | } | |||
| 184 | ||||
| 185 | Buffer result = Split(c - buf); | |||
| 186 | // Remove the aNeedle character itself. | |||
| 187 | Split(1); | |||
| 188 | return result; | |||
| 189 | } | |||
| 190 | ||||
| 191 | // Advance to the position after aNeedle. This is like SplitChar but does not | |||
| 192 | // return the skipped portion. | |||
| 193 | void Skip(char aNeedle, unsigned nTimes = 1) { | |||
| 194 | for (unsigned i = 0; i < nTimes; i++) { | |||
| 195 | SplitChar(aNeedle); | |||
| 196 | } | |||
| 197 | } | |||
| 198 | ||||
| 199 | void SkipWhitespace() { | |||
| 200 | while (mLength > 0) { | |||
| 201 | if (!IsSpace(mBuf[0])) { | |||
| 202 | break; | |||
| 203 | } | |||
| 204 | mBuf++; | |||
| 205 | mLength--; | |||
| 206 | } | |||
| 207 | } | |||
| 208 | ||||
| 209 | static bool IsSpace(char c) { | |||
| 210 | switch (c) { | |||
| 211 | case ' ': | |||
| 212 | case '\t': | |||
| 213 | case '\n': | |||
| 214 | case '\v': | |||
| 215 | case '\f': | |||
| 216 | case '\r': | |||
| 217 | return true; | |||
| 218 | } | |||
| 219 | return false; | |||
| 220 | } | |||
| 221 | ||||
| 222 | /* Returns a sub-buffer of at most aLength characters. The "parent" buffer is | |||
| 223 | * amputated of those aLength characters. If the "parent" buffer is smaller | |||
| 224 | * than aLength, then its length is used instead. */ | |||
| 225 | Buffer Split(size_t aLength) { | |||
| 226 | Buffer result(mBuf, std::min(aLength, mLength)); | |||
| 227 | mLength -= result.mLength; | |||
| 228 | mBuf += result.mLength; | |||
| 229 | return result; | |||
| 230 | } | |||
| 231 | ||||
| 232 | /* Move the buffer (including its content) to the memory address of the aOther | |||
| 233 | * buffer. */ | |||
| 234 | void Slide(Buffer aOther) { | |||
| 235 | memmove(const_cast<char*>(aOther.mBuf), mBuf, mLength); | |||
| 236 | mBuf = aOther.mBuf; | |||
| 237 | } | |||
| 238 | ||||
| 239 | /* Returns whether the two involved buffers have the same content. */ | |||
| 240 | bool operator==(Buffer aOther) { | |||
| 241 | return mLength == aOther.mLength && | |||
| 242 | (mBuf == aOther.mBuf || !strncmp(mBuf, aOther.mBuf, mLength)); | |||
| 243 | } | |||
| 244 | ||||
| 245 | bool operator!=(Buffer aOther) { return !(*this == aOther); } | |||
| 246 | ||||
| 247 | /* Returns true if the buffer is not empty. */ | |||
| 248 | explicit operator bool() { return mLength; } | |||
| 249 | ||||
| 250 | char operator[](size_t n) const { return mBuf[n]; } | |||
| 251 | ||||
| 252 | /* Returns the memory location of the buffer. */ | |||
| 253 | const char* get() { return mBuf; } | |||
| 254 | ||||
| 255 | /* Returns the memory location of the end of the buffer (technically, the | |||
| 256 | * first byte after the buffer). */ | |||
| 257 | const char* GetEnd() { return mBuf + mLength; } | |||
| 258 | ||||
| 259 | /* Extend the buffer over the content of the other buffer, assuming it is | |||
| 260 | * adjacent. */ | |||
| 261 | void Extend(Buffer aOther) { | |||
| 262 | MOZ_ASSERT(aOther.mBuf == GetEnd())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOther.mBuf == GetEnd())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOther.mBuf == GetEnd()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("aOther.mBuf == GetEnd()" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 262); do { } while (false); do { MOZ_CrashSequence(__null, 262 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 263 | mLength += aOther.mLength; | |||
| 264 | } | |||
| 265 | ||||
| 266 | size_t Length() const { return mLength; } | |||
| 267 | ||||
| 268 | private: | |||
| 269 | const char* mBuf; | |||
| 270 | size_t mLength; | |||
| 271 | }; | |||
| 272 | ||||
| 273 | /* Helper class to read from a file descriptor line by line. */ | |||
| 274 | class FdReader { | |||
| 275 | public: | |||
| 276 | explicit FdReader(int aFd, bool aNeedClose = false) | |||
| 277 | : mFd(aFd), | |||
| 278 | mNeedClose(aNeedClose), | |||
| 279 | mData(&mRawBuf, 0), | |||
| 280 | mBuf(&mRawBuf, sizeof(mRawBuf)) {} | |||
| 281 | ||||
| 282 | FdReader(FdReader&& aOther) noexcept | |||
| 283 | : mFd(aOther.mFd), | |||
| 284 | mNeedClose(aOther.mNeedClose), | |||
| 285 | mData(&mRawBuf, 0), | |||
| 286 | mBuf(&mRawBuf, sizeof(mRawBuf)) { | |||
| 287 | memcpy(mRawBuf, aOther.mRawBuf, sizeof(mRawBuf)); | |||
Other elements might also be undefined | ||||
| ||||
| 288 | aOther.mFd = -1; | |||
| 289 | aOther.mNeedClose = false; | |||
| 290 | aOther.mData = Buffer(); | |||
| 291 | aOther.mBuf = Buffer(); | |||
| 292 | } | |||
| 293 | ||||
| 294 | FdReader& operator=(const FdReader&) = delete; | |||
| 295 | FdReader(const FdReader&) = delete; | |||
| 296 | ||||
| 297 | ~FdReader() { | |||
| 298 | if (mNeedClose) { | |||
| 299 | close(mFd); | |||
| 300 | } | |||
| 301 | } | |||
| 302 | ||||
| 303 | /* Read a line from the file descriptor and returns it as a Buffer instance */ | |||
| 304 | Buffer ReadLine() { | |||
| 305 | while (true) { | |||
| 306 | Buffer result = mData.SplitChar('\n'); | |||
| 307 | ||||
| 308 | /* There are essentially three different cases here: | |||
| 309 | * - '\n' was found "early". In this case, the end of the result buffer | |||
| 310 | * is before the beginning of the mData buffer (since SplitChar | |||
| 311 | * amputated it). | |||
| 312 | * - '\n' was found as the last character of mData. In this case, mData | |||
| 313 | * is empty, but still points at the end of mBuf. result points to what | |||
| 314 | * used to be in mData, without the last character. | |||
| 315 | * - '\n' was not found. In this case too, mData is empty and points at | |||
| 316 | * the end of mBuf. But result points to the entire buffer that used to | |||
| 317 | * be pointed by mData. | |||
| 318 | * Only in the latter case do both result and mData's end match, and it's | |||
| 319 | * the only case where we need to refill the buffer. | |||
| 320 | */ | |||
| 321 | if (result.GetEnd() != mData.GetEnd()) { | |||
| 322 | return result; | |||
| 323 | } | |||
| 324 | ||||
| 325 | /* Since SplitChar emptied mData, make it point to what it had before. */ | |||
| 326 | mData = result; | |||
| 327 | ||||
| 328 | /* And move it to the beginning of the read buffer. */ | |||
| 329 | mData.Slide(mBuf); | |||
| 330 | ||||
| 331 | FillBuffer(); | |||
| 332 | ||||
| 333 | if (!mData) { | |||
| 334 | return Buffer(); | |||
| 335 | } | |||
| 336 | } | |||
| 337 | } | |||
| 338 | ||||
| 339 | private: | |||
| 340 | /* Fill the read buffer. */ | |||
| 341 | void FillBuffer() { | |||
| 342 | size_t size = mBuf.GetEnd() - mData.GetEnd(); | |||
| 343 | Buffer remainder(mData.GetEnd(), size); | |||
| 344 | ||||
| 345 | ssize_t len = 1; | |||
| 346 | while (remainder && len > 0) { | |||
| 347 | len = ::read(mFd, const_cast<char*>(remainder.get()), size); | |||
| 348 | if (len < 0) { | |||
| 349 | die("Read error"); | |||
| 350 | } | |||
| 351 | size -= len; | |||
| 352 | mData.Extend(remainder.Split(len)); | |||
| 353 | } | |||
| 354 | } | |||
| 355 | ||||
| 356 | /* File descriptor to read from. */ | |||
| 357 | int mFd; | |||
| 358 | bool mNeedClose; | |||
| 359 | ||||
| 360 | /* Part of data that was read from the file descriptor but not returned with | |||
| 361 | * ReadLine yet. */ | |||
| 362 | Buffer mData; | |||
| 363 | /* Buffer representation of mRawBuf */ | |||
| 364 | Buffer mBuf; | |||
| 365 | /* read() buffer */ | |||
| 366 | char mRawBuf[4096]; | |||
| 367 | }; | |||
| 368 | ||||
| 369 | MOZ_BEGIN_EXTERN_Cextern "C" { | |||
| 370 | ||||
| 371 | /* Function declarations for all the replace_malloc _impl functions. | |||
| 372 | * See memory/build/replace_malloc.c */ | |||
| 373 | #define MALLOC_DECL(name, return_type, ...) \ | |||
| 374 | return_type name##_impl(__VA_ARGS__); | |||
| 375 | #define MALLOC_FUNCS MALLOC_FUNCS_MALLOC(1 | 2) | |||
| 376 | #include "malloc_decls.h" | |||
| 377 | ||||
| 378 | #define MALLOC_DECL(name, return_type, ...) return_type name(__VA_ARGS__); | |||
| 379 | #define MALLOC_FUNCS MALLOC_FUNCS_JEMALLOC4 | |||
| 380 | #include "malloc_decls.h" | |||
| 381 | ||||
| 382 | MOZ_END_EXTERN_C} | |||
| 383 | ||||
| 384 | template <unsigned Base = 10> | |||
| 385 | size_t parseNumber(Buffer aBuf) { | |||
| 386 | if (!aBuf) { | |||
| 387 | die("Malformed input"); | |||
| 388 | } | |||
| 389 | ||||
| 390 | size_t result = 0; | |||
| 391 | for (const char *c = aBuf.get(), *end = aBuf.GetEnd(); c < end; c++) { | |||
| 392 | result *= Base; | |||
| 393 | if ((*c >= '0' && *c <= '9')) { | |||
| 394 | result += *c - '0'; | |||
| 395 | } else if (Base == 16 && *c >= 'a' && *c <= 'f') { | |||
| 396 | result += *c - 'a' + 10; | |||
| 397 | } else if (Base == 16 && *c >= 'A' && *c <= 'F') { | |||
| 398 | result += *c - 'A' + 10; | |||
| 399 | } else { | |||
| 400 | die("Malformed input"); | |||
| 401 | } | |||
| 402 | } | |||
| 403 | return result; | |||
| 404 | } | |||
| 405 | ||||
| 406 | static size_t percent(size_t a, size_t b) { | |||
| 407 | if (!b) { | |||
| 408 | return 0; | |||
| 409 | } | |||
| 410 | return size_t(round(double(a) / double(b) * 100.0)); | |||
| 411 | } | |||
| 412 | ||||
| 413 | class Distribution { | |||
| 414 | public: | |||
| 415 | // Default constructor used for array initialisation. | |||
| 416 | Distribution() | |||
| 417 | : mMaxSize(0), | |||
| 418 | mNextSmallest(0), | |||
| 419 | mShift(0), | |||
| 420 | mArrayOffset(0), | |||
| 421 | mArraySlots(0), | |||
| 422 | mTotalRequests(0), | |||
| 423 | mRequests{0} {} | |||
| 424 | ||||
| 425 | Distribution(size_t max_size, size_t next_smallest, size_t bucket_size) | |||
| 426 | : mMaxSize(max_size), | |||
| 427 | mNextSmallest(next_smallest), | |||
| 428 | mShift(CeilingLog2(bucket_size)), | |||
| 429 | mArrayOffset(1 + next_smallest), | |||
| 430 | mArraySlots((max_size - next_smallest) >> mShift), | |||
| 431 | mTotalRequests(0), | |||
| 432 | mRequests{ | |||
| 433 | 0, | |||
| 434 | } { | |||
| 435 | MOZ_ASSERT(mMaxSize)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mMaxSize)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mMaxSize))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mMaxSize", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 435); do { } while (false); do { MOZ_CrashSequence(__null, 435 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 436 | MOZ_RELEASE_ASSERT(mArraySlots <= MAX_NUM_BUCKETS)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mArraySlots <= MAX_NUM_BUCKETS)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mArraySlots <= MAX_NUM_BUCKETS ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "mArraySlots <= MAX_NUM_BUCKETS", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 436); do { } while (false); do { MOZ_CrashSequence(__null, 436 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 437 | } | |||
| 438 | ||||
| 439 | Distribution& operator=(const Distribution& aOther) = default; | |||
| 440 | ||||
| 441 | void addRequest(size_t request) { | |||
| 442 | MOZ_ASSERT(mMaxSize)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mMaxSize)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mMaxSize))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mMaxSize", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 442); do { } while (false); do { MOZ_CrashSequence(__null, 442 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 443 | ||||
| 444 | mRequests[(request - mArrayOffset) >> mShift]++; | |||
| 445 | mTotalRequests++; | |||
| 446 | } | |||
| 447 | ||||
| 448 | void printDist(platform_handle_t std_err) { | |||
| 449 | MOZ_ASSERT(mMaxSize)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mMaxSize)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mMaxSize))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mMaxSize", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 449); do { } while (false); do { MOZ_CrashSequence(__null, 449 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 450 | ||||
| 451 | // The translation to turn a slot index into a memory request size. | |||
| 452 | const size_t array_offset_add = (1 << mShift) + mNextSmallest; | |||
| 453 | ||||
| 454 | FdPrintf(std_err, "\n%zu-bin Distribution:\n", mMaxSize); | |||
| 455 | FdPrintf(std_err, " request : count percent\n"); | |||
| 456 | size_t range_start = mNextSmallest + 1; | |||
| 457 | for (size_t j = 0; j < mArraySlots; j++) { | |||
| 458 | size_t range_end = (j << mShift) + array_offset_add; | |||
| 459 | FdPrintf(std_err, "%5zu - %5zu: %6zu %6zu%%\n", range_start, range_end, | |||
| 460 | mRequests[j], percent(mRequests[j], mTotalRequests)); | |||
| 461 | range_start = range_end + 1; | |||
| 462 | } | |||
| 463 | } | |||
| 464 | ||||
| 465 | size_t maxSize() const { return mMaxSize; } | |||
| 466 | ||||
| 467 | private: | |||
| 468 | static constexpr size_t MAX_NUM_BUCKETS = 16; | |||
| 469 | ||||
| 470 | // If size is zero this distribution is uninitialised. | |||
| 471 | size_t mMaxSize; | |||
| 472 | size_t mNextSmallest; | |||
| 473 | ||||
| 474 | // Parameters to convert a size into a slot number. | |||
| 475 | unsigned mShift; | |||
| 476 | unsigned mArrayOffset; | |||
| 477 | ||||
| 478 | // The number of slots. | |||
| 479 | unsigned mArraySlots; | |||
| 480 | ||||
| 481 | size_t mTotalRequests; | |||
| 482 | size_t mRequests[MAX_NUM_BUCKETS]; | |||
| 483 | }; | |||
| 484 | ||||
| 485 | #ifdef XP_LINUX1 | |||
| 486 | struct MemoryMap { | |||
| 487 | uintptr_t mStart; | |||
| 488 | uintptr_t mEnd; | |||
| 489 | bool mReadable; | |||
| 490 | bool mPrivate; | |||
| 491 | bool mAnon; | |||
| 492 | bool mIsStack; | |||
| 493 | bool mIsSpecial; | |||
| 494 | size_t mRSS; | |||
| 495 | ||||
| 496 | bool IsCandidate() const { | |||
| 497 | // Candidates mappings are: | |||
| 498 | // * anonymous | |||
| 499 | // * they are private (not shared), | |||
| 500 | // * anonymous or "[heap]" (not another area such as stack), | |||
| 501 | // | |||
| 502 | // The only mappings we're falsely including are the .bss segments for | |||
| 503 | // shared libraries. | |||
| 504 | return mReadable && mPrivate && mAnon && !mIsStack && !mIsSpecial; | |||
| 505 | } | |||
| 506 | }; | |||
| 507 | ||||
| 508 | class SMapsReader : private FdReader { | |||
| 509 | private: | |||
| 510 | explicit SMapsReader(FdReader&& reader) : FdReader(std::move(reader)) {} | |||
| 511 | ||||
| 512 | public: | |||
| 513 | static Maybe<SMapsReader> open() { | |||
| 514 | int fd = ::open(FILENAME, O_RDONLY00); | |||
| 515 | if (fd < 0) { | |||
| 516 | perror(FILENAME); | |||
| 517 | return mozilla::Nothing(); | |||
| 518 | } | |||
| 519 | ||||
| 520 | return Some(SMapsReader(FdReader(fd, true))); | |||
| 521 | } | |||
| 522 | ||||
| 523 | Maybe<MemoryMap> readMap(platform_handle_t aStdErr) { | |||
| 524 | // This is not very tolerant of format changes because things like | |||
| 525 | // parseNumber will crash if they get a bad value. TODO: make this | |||
| 526 | // soft-fail. | |||
| 527 | ||||
| 528 | Buffer line = ReadLine(); | |||
| 529 | if (!line) { | |||
| 530 | return Nothing(); | |||
| 531 | } | |||
| 532 | ||||
| 533 | // We're going to be at the start of an entry, start tokenising the first | |||
| 534 | // line. | |||
| 535 | ||||
| 536 | // Range | |||
| 537 | Buffer range = line.SplitChar(' '); | |||
| 538 | uintptr_t range_start = parseNumber<16>(range.SplitChar('-')); | |||
| 539 | uintptr_t range_end = parseNumber<16>(range); | |||
| 540 | ||||
| 541 | // Mode. | |||
| 542 | Buffer mode = line.SplitChar(' '); | |||
| 543 | if (mode.Length() != 4) { | |||
| 544 | FdPrintf(aStdErr, "Couldn't parse SMAPS file\n"); | |||
| 545 | return Nothing(); | |||
| 546 | } | |||
| 547 | bool readable = mode[0] == 'r'; | |||
| 548 | bool private_ = mode[3] == 'p'; | |||
| 549 | ||||
| 550 | // Offset, device and inode. | |||
| 551 | line.SkipWhitespace(); | |||
| 552 | bool zero_offset = !parseNumber<16>(line.SplitChar(' ')); | |||
| 553 | line.SkipWhitespace(); | |||
| 554 | bool no_device = line.SplitChar(' ') == Buffer("00:00"); | |||
| 555 | line.SkipWhitespace(); | |||
| 556 | bool zero_inode = !parseNumber(line.SplitChar(' ')); | |||
| 557 | bool is_anon = zero_offset && no_device && zero_inode; | |||
| 558 | ||||
| 559 | // Filename, or empty for anon mappings. | |||
| 560 | line.SkipWhitespace(); | |||
| 561 | Buffer filename = line.SplitChar(' '); | |||
| 562 | ||||
| 563 | bool is_stack; | |||
| 564 | bool is_special; | |||
| 565 | if (filename && filename[0] == '[') { | |||
| 566 | is_stack = filename == Buffer("[stack]"); | |||
| 567 | is_special = filename == Buffer("[vdso]") || | |||
| 568 | filename == Buffer("[vvar]") || | |||
| 569 | filename == Buffer("[vsyscall]"); | |||
| 570 | } else { | |||
| 571 | is_stack = false; | |||
| 572 | is_special = false; | |||
| 573 | } | |||
| 574 | ||||
| 575 | size_t rss = 0; | |||
| 576 | while ((line = ReadLine())) { | |||
| 577 | Buffer field = line.SplitChar(':'); | |||
| 578 | if (field == Buffer("VmFlags")) { | |||
| 579 | // This is the last field, at least in the current format. Break this | |||
| 580 | // loop to read the next mapping. | |||
| 581 | break; | |||
| 582 | } | |||
| 583 | ||||
| 584 | if (field == Buffer("Rss")) { | |||
| 585 | line.SkipWhitespace(); | |||
| 586 | Buffer value = line.SplitChar(' '); | |||
| 587 | rss = parseNumber(value) * 1024; | |||
| 588 | } | |||
| 589 | } | |||
| 590 | ||||
| 591 | return Some(MemoryMap({range_start, range_end, readable, private_, is_anon, | |||
| 592 | is_stack, is_special, rss})); | |||
| 593 | } | |||
| 594 | ||||
| 595 | static constexpr char FILENAME[] = "/proc/self/smaps"; | |||
| 596 | }; | |||
| 597 | #endif // XP_LINUX | |||
| 598 | ||||
| 599 | /* Class to handle dispatching the replay function calls to replace-malloc. */ | |||
| 600 | class Replay { | |||
| 601 | public: | |||
| 602 | Replay() { | |||
| 603 | #ifdef _WIN32 | |||
| 604 | // See comment in FdPrintf.h as to why native win32 handles are used. | |||
| 605 | mStdErr = GetStdHandle(STD_ERROR_HANDLE); | |||
| 606 | #else | |||
| 607 | mStdErr = fileno(stderrstderr); | |||
| 608 | #endif | |||
| 609 | #ifdef XP_LINUX1 | |||
| 610 | BuildInitialMapInfo(); | |||
| ||||
| 611 | #endif | |||
| 612 | } | |||
| 613 | ||||
| 614 | void enableSlopCalculation() { mCalculateSlop = true; } | |||
| 615 | void enableMemset() { mDoMemset = true; } | |||
| 616 | ||||
| 617 | MemSlot& operator[](size_t index) const { return mSlots[index]; } | |||
| 618 | ||||
| 619 | void malloc(Buffer& aArgs, Buffer& aResult) { | |||
| 620 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 621 | mOps++; | |||
| 622 | size_t size = parseNumber(aArgs); | |||
| 623 | aSlot.mPtr = ::malloc_implmalloc(size); | |||
| 624 | if (aSlot.mPtr) { | |||
| 625 | aSlot.mRequest = size; | |||
| 626 | MaybeCommit(aSlot); | |||
| 627 | if (mCalculateSlop) { | |||
| 628 | mTotalRequestedSize += size; | |||
| 629 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 630 | } | |||
| 631 | } | |||
| 632 | } | |||
| 633 | ||||
| 634 | void posix_memalign(Buffer& aArgs, Buffer& aResult) { | |||
| 635 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 636 | mOps++; | |||
| 637 | size_t alignment = parseNumber(aArgs.SplitChar(',')); | |||
| 638 | size_t size = parseNumber(aArgs); | |||
| 639 | void* ptr; | |||
| 640 | if (::posix_memalign_implposix_memalign(&ptr, alignment, size) == 0) { | |||
| 641 | aSlot.mPtr = ptr; | |||
| 642 | aSlot.mRequest = size; | |||
| 643 | MaybeCommit(aSlot); | |||
| 644 | if (mCalculateSlop) { | |||
| 645 | mTotalRequestedSize += size; | |||
| 646 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 647 | } | |||
| 648 | } else { | |||
| 649 | aSlot.mPtr = nullptr; | |||
| 650 | } | |||
| 651 | } | |||
| 652 | ||||
| 653 | void aligned_alloc(Buffer& aArgs, Buffer& aResult) { | |||
| 654 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 655 | mOps++; | |||
| 656 | size_t alignment = parseNumber(aArgs.SplitChar(',')); | |||
| 657 | size_t size = parseNumber(aArgs); | |||
| 658 | aSlot.mPtr = ::aligned_alloc_implaligned_alloc(alignment, size); | |||
| 659 | if (aSlot.mPtr) { | |||
| 660 | aSlot.mRequest = size; | |||
| 661 | MaybeCommit(aSlot); | |||
| 662 | if (mCalculateSlop) { | |||
| 663 | mTotalRequestedSize += size; | |||
| 664 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 665 | } | |||
| 666 | } | |||
| 667 | } | |||
| 668 | ||||
| 669 | void calloc(Buffer& aArgs, Buffer& aResult) { | |||
| 670 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 671 | mOps++; | |||
| 672 | size_t num = parseNumber(aArgs.SplitChar(',')); | |||
| 673 | size_t size = parseNumber(aArgs); | |||
| 674 | aSlot.mPtr = ::calloc_implcalloc(num, size); | |||
| 675 | if (aSlot.mPtr) { | |||
| 676 | aSlot.mRequest = num * size; | |||
| 677 | MaybeCommit(aSlot); | |||
| 678 | if (mCalculateSlop) { | |||
| 679 | mTotalRequestedSize += num * size; | |||
| 680 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 681 | } | |||
| 682 | } | |||
| 683 | } | |||
| 684 | ||||
| 685 | void realloc(Buffer& aArgs, Buffer& aResult) { | |||
| 686 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 687 | mOps++; | |||
| 688 | Buffer dummy = aArgs.SplitChar('#'); | |||
| 689 | if (dummy) { | |||
| 690 | die("Malformed input"); | |||
| 691 | } | |||
| 692 | size_t slot_id = parseNumber(aArgs.SplitChar(',')); | |||
| 693 | size_t size = parseNumber(aArgs); | |||
| 694 | MemSlot& old_slot = (*this)[slot_id]; | |||
| 695 | void* old_ptr = old_slot.mPtr; | |||
| 696 | old_slot.mPtr = nullptr; | |||
| 697 | aSlot.mPtr = ::realloc_implrealloc(old_ptr, size); | |||
| 698 | if (aSlot.mPtr) { | |||
| 699 | aSlot.mRequest = size; | |||
| 700 | MaybeCommit(aSlot); | |||
| 701 | if (mCalculateSlop) { | |||
| 702 | mTotalRequestedSize += size; | |||
| 703 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 704 | } | |||
| 705 | } | |||
| 706 | } | |||
| 707 | ||||
| 708 | void free(Buffer& aArgs, Buffer& aResult) { | |||
| 709 | if (aResult) { | |||
| 710 | die("Malformed input"); | |||
| 711 | } | |||
| 712 | mOps++; | |||
| 713 | Buffer dummy = aArgs.SplitChar('#'); | |||
| 714 | if (dummy) { | |||
| 715 | die("Malformed input"); | |||
| 716 | } | |||
| 717 | size_t slot_id = parseNumber(aArgs); | |||
| 718 | MemSlot& slot = (*this)[slot_id]; | |||
| 719 | ::free_implfree(slot.mPtr); | |||
| 720 | slot.mPtr = nullptr; | |||
| 721 | } | |||
| 722 | ||||
| 723 | void memalign(Buffer& aArgs, Buffer& aResult) { | |||
| 724 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 725 | mOps++; | |||
| 726 | size_t alignment = parseNumber(aArgs.SplitChar(',')); | |||
| 727 | size_t size = parseNumber(aArgs); | |||
| 728 | aSlot.mPtr = ::memalign_implmemalign(alignment, size); | |||
| 729 | if (aSlot.mPtr) { | |||
| 730 | aSlot.mRequest = size; | |||
| 731 | MaybeCommit(aSlot); | |||
| 732 | if (mCalculateSlop) { | |||
| 733 | mTotalRequestedSize += size; | |||
| 734 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 735 | } | |||
| 736 | } | |||
| 737 | } | |||
| 738 | ||||
| 739 | void valloc(Buffer& aArgs, Buffer& aResult) { | |||
| 740 | MemSlot& aSlot = SlotForResult(aResult); | |||
| 741 | mOps++; | |||
| 742 | size_t size = parseNumber(aArgs); | |||
| 743 | aSlot.mPtr = ::valloc_implvalloc(size); | |||
| 744 | if (aSlot.mPtr) { | |||
| 745 | aSlot.mRequest = size; | |||
| 746 | MaybeCommit(aSlot); | |||
| 747 | if (mCalculateSlop) { | |||
| 748 | mTotalRequestedSize += size; | |||
| 749 | mTotalAllocatedSize += ::malloc_usable_size_implmalloc_usable_size(aSlot.mPtr); | |||
| 750 | } | |||
| 751 | } | |||
| 752 | } | |||
| 753 | ||||
| 754 | void jemalloc_stats(Buffer& aArgs, Buffer& aResult) { | |||
| 755 | if (aArgs || aResult) { | |||
| 756 | die("Malformed input"); | |||
| 757 | } | |||
| 758 | mOps++; | |||
| 759 | jemalloc_stats_t stats; | |||
| 760 | // Using a variable length array here is a GCC & Clang extension. But it | |||
| 761 | // allows us to place this on the stack and not alter jemalloc's profiling. | |||
| 762 | const size_t num_bins = ::jemalloc_stats_num_bins(); | |||
| 763 | const size_t MAX_NUM_BINS = 100; | |||
| 764 | if (num_bins > MAX_NUM_BINS) { | |||
| 765 | die("Exceeded maximum number of jemalloc stats bins"); | |||
| 766 | } | |||
| 767 | jemalloc_bin_stats_t bin_stats[MAX_NUM_BINS] = {{0}}; | |||
| 768 | ::jemalloc_stats_internal(&stats, bin_stats); | |||
| 769 | ||||
| 770 | #ifdef XP_LINUX1 | |||
| 771 | size_t rss = get_rss(); | |||
| 772 | #endif | |||
| 773 | ||||
| 774 | size_t num_objects = 0; | |||
| 775 | size_t num_sloppy_objects = 0; | |||
| 776 | size_t total_allocated = 0; | |||
| 777 | size_t total_slop = 0; | |||
| 778 | size_t large_slop = 0; | |||
| 779 | size_t large_used = 0; | |||
| 780 | size_t huge_slop = 0; | |||
| 781 | size_t huge_used = 0; | |||
| 782 | size_t bin_slop[MAX_NUM_BINS] = {0}; | |||
| 783 | ||||
| 784 | for (size_t slot_id = 0; slot_id < mNumUsedSlots; slot_id++) { | |||
| 785 | MemSlot& slot = mSlots[slot_id]; | |||
| 786 | if (slot.mPtr) { | |||
| 787 | size_t used = ::malloc_usable_size_implmalloc_usable_size(slot.mPtr); | |||
| 788 | size_t slop = used - slot.mRequest; | |||
| 789 | total_allocated += used; | |||
| 790 | total_slop += slop; | |||
| 791 | num_objects++; | |||
| 792 | if (slop) { | |||
| 793 | num_sloppy_objects++; | |||
| 794 | } | |||
| 795 | ||||
| 796 | if (used <= | |||
| 797 | (stats.subpage_max ? stats.subpage_max : stats.quantum_wide_max)) { | |||
| 798 | // We know that this is an inefficient linear search, but there's a | |||
| 799 | // small number of bins and this is simple. | |||
| 800 | for (unsigned i = 0; i < num_bins; i++) { | |||
| 801 | auto& bin = bin_stats[i]; | |||
| 802 | if (used == bin.size) { | |||
| 803 | bin_slop[i] += slop; | |||
| 804 | break; | |||
| 805 | } | |||
| 806 | } | |||
| 807 | } else if (used <= stats.large_max) { | |||
| 808 | large_slop += slop; | |||
| 809 | large_used += used; | |||
| 810 | } else { | |||
| 811 | huge_slop += slop; | |||
| 812 | huge_used += used; | |||
| 813 | } | |||
| 814 | } | |||
| 815 | } | |||
| 816 | ||||
| 817 | // This formula corresponds to the calculation of wasted (from committed and | |||
| 818 | // the other parameters) within jemalloc_stats() | |||
| 819 | size_t committed = stats.allocated + stats.waste + stats.pages_dirty + | |||
| 820 | stats.bookkeeping + stats.bin_unused; | |||
| 821 | ||||
| 822 | FdPrintf(mStdErr, "\n"); | |||
| 823 | FdPrintf(mStdErr, "Objects: %9zu\n", num_objects); | |||
| 824 | FdPrintf(mStdErr, "Slots: %9zu\n", mNumUsedSlots); | |||
| 825 | FdPrintf(mStdErr, "Ops: %9zu\n", mOps); | |||
| 826 | FdPrintf(mStdErr, "mapped: %9zu\n", stats.mapped); | |||
| 827 | FdPrintf(mStdErr, "committed: %9zu\n", committed); | |||
| 828 | #ifdef XP_LINUX1 | |||
| 829 | if (rss) { | |||
| 830 | FdPrintf(mStdErr, "rss: %9zu\n", rss); | |||
| 831 | } | |||
| 832 | #endif | |||
| 833 | FdPrintf(mStdErr, "allocated: %9zu\n", stats.allocated); | |||
| 834 | FdPrintf(mStdErr, "waste: %9zu\n", stats.waste); | |||
| 835 | FdPrintf(mStdErr, "dirty: %9zu\n", stats.pages_dirty); | |||
| 836 | FdPrintf(mStdErr, "fresh: %9zu\n", stats.pages_fresh); | |||
| 837 | FdPrintf(mStdErr, "madvised: %9zu\n", stats.pages_madvised); | |||
| 838 | FdPrintf(mStdErr, "bookkeep: %9zu\n", stats.bookkeeping); | |||
| 839 | FdPrintf(mStdErr, "bin-unused: %9zu\n", stats.bin_unused); | |||
| 840 | FdPrintf(mStdErr, "quantum-max: %9zu\n", stats.quantum_max); | |||
| 841 | FdPrintf(mStdErr, "quantum-wide-max: %9zu\n", stats.quantum_wide_max); | |||
| 842 | FdPrintf(mStdErr, "subpage-max: %9zu\n", stats.subpage_max); | |||
| 843 | FdPrintf(mStdErr, "large-max: %9zu\n", stats.large_max); | |||
| 844 | if (mCalculateSlop) { | |||
| 845 | size_t slop = mTotalAllocatedSize - mTotalRequestedSize; | |||
| 846 | FdPrintf(mStdErr, | |||
| 847 | "Total slop for all allocations: %zuKiB/%zuKiB (%zu%%)\n", | |||
| 848 | slop / 1024, mTotalAllocatedSize / 1024, | |||
| 849 | percent(slop, mTotalAllocatedSize)); | |||
| 850 | } | |||
| 851 | FdPrintf(mStdErr, "Live sloppy objects: %zu/%zu (%zu%%)\n", | |||
| 852 | num_sloppy_objects, num_objects, | |||
| 853 | percent(num_sloppy_objects, num_objects)); | |||
| 854 | FdPrintf(mStdErr, "Live sloppy bytes: %zuKiB/%zuKiB (%zu%%)\n", | |||
| 855 | total_slop / 1024, total_allocated / 1024, | |||
| 856 | percent(total_slop, total_allocated)); | |||
| 857 | ||||
| 858 | FdPrintf(mStdErr, "\n%8s %11s %10s %8s %9s %9s %8s\n", "bin-size", | |||
| 859 | "unused (c)", "total (c)", "used (c)", "non-full (r)", "total (r)", | |||
| 860 | "used (r)"); | |||
| 861 | for (unsigned i = 0; i < num_bins; i++) { | |||
| 862 | auto& bin = bin_stats[i]; | |||
| 863 | MOZ_ASSERT(bin.size)do { static_assert( mozilla::detail::AssertionConditionType< decltype(bin.size)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(bin.size))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("bin.size", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 863); do { } while (false); do { MOZ_CrashSequence(__null, 863 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 864 | FdPrintf(mStdErr, "%8zu %8zuKiB %7zuKiB %7zu%% %12zu %9zu %7zu%%\n", | |||
| 865 | bin.size, bin.bytes_unused / 1024, bin.bytes_total / 1024, | |||
| 866 | percent(bin.bytes_total - bin.bytes_unused, bin.bytes_total), | |||
| 867 | bin.num_non_full_runs, bin.num_runs, | |||
| 868 | percent(bin.num_runs - bin.num_non_full_runs, bin.num_runs)); | |||
| 869 | } | |||
| 870 | ||||
| 871 | FdPrintf(mStdErr, "\n%5s %8s %9s %7s\n", "bin", "slop", "used", "percent"); | |||
| 872 | for (unsigned i = 0; i < num_bins; i++) { | |||
| 873 | auto& bin = bin_stats[i]; | |||
| 874 | size_t used = bin.bytes_total - bin.bytes_unused; | |||
| 875 | FdPrintf(mStdErr, "%5zu %8zu %9zu %6zu%%\n", bin.size, bin_slop[i], used, | |||
| 876 | percent(bin_slop[i], used)); | |||
| 877 | } | |||
| 878 | FdPrintf(mStdErr, "%5s %8zu %9zu %6zu%%\n", "large", large_slop, large_used, | |||
| 879 | percent(large_slop, large_used)); | |||
| 880 | FdPrintf(mStdErr, "%5s %8zu %9zu %6zu%%\n", "huge", huge_slop, huge_used, | |||
| 881 | percent(huge_slop, huge_used)); | |||
| 882 | ||||
| 883 | print_distributions(stats, bin_stats); | |||
| 884 | } | |||
| 885 | ||||
| 886 | private: | |||
| 887 | /* | |||
| 888 | * Create and print frequency distributions of memory requests. | |||
| 889 | */ | |||
| 890 | void print_distributions(jemalloc_stats_t& stats, | |||
| 891 | jemalloc_bin_stats_t* bin_stats) { | |||
| 892 | const size_t num_bins = ::jemalloc_stats_num_bins(); | |||
| 893 | ||||
| 894 | // We compute distributions for all of the bins for small allocations | |||
| 895 | // (num_bins) plus two more distributions for larger allocations. | |||
| 896 | Distribution dists[num_bins + 2]; | |||
| 897 | ||||
| 898 | unsigned last_size = 0; | |||
| 899 | unsigned num_dists = 0; | |||
| 900 | for (unsigned i = 0; i < num_bins; i++) { | |||
| 901 | auto& bin = bin_stats[i]; | |||
| 902 | auto& dist = dists[num_dists++]; | |||
| 903 | ||||
| 904 | MOZ_ASSERT(bin.size)do { static_assert( mozilla::detail::AssertionConditionType< decltype(bin.size)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(bin.size))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("bin.size", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 904); do { } while (false); do { MOZ_CrashSequence(__null, 904 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 905 | if (bin.size <= 16) { | |||
| 906 | // 1 byte buckets. | |||
| 907 | dist = Distribution(bin.size, last_size, 1); | |||
| 908 | } else if (bin.size <= stats.quantum_max) { | |||
| 909 | // 4 buckets, (4 bytes per bucket with a 16 byte quantum). | |||
| 910 | dist = Distribution(bin.size, last_size, stats.quantum / 4); | |||
| 911 | } else if (bin.size <= stats.quantum_wide_max) { | |||
| 912 | // 8 buckets, (32 bytes per bucket with a 256 byte quantum-wide). | |||
| 913 | dist = Distribution(bin.size, last_size, stats.quantum_wide / 8); | |||
| 914 | } else { | |||
| 915 | // 16 buckets. | |||
| 916 | dist = Distribution(bin.size, last_size, (bin.size - last_size) / 16); | |||
| 917 | } | |||
| 918 | last_size = bin.size; | |||
| 919 | } | |||
| 920 | ||||
| 921 | // 16 buckets. | |||
| 922 | dists[num_dists] = Distribution(stats.page_size, last_size, | |||
| 923 | (stats.page_size - last_size) / 16); | |||
| 924 | num_dists++; | |||
| 925 | ||||
| 926 | // Buckets are 1/4 of the page size (12 buckets). | |||
| 927 | dists[num_dists] = | |||
| 928 | Distribution(stats.page_size * 4, stats.page_size, stats.page_size / 4); | |||
| 929 | num_dists++; | |||
| 930 | ||||
| 931 | MOZ_RELEASE_ASSERT(num_dists <= num_bins + 2)do { static_assert( mozilla::detail::AssertionConditionType< decltype(num_dists <= num_bins + 2)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(num_dists <= num_bins + 2 ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "num_dists <= num_bins + 2", "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 931); do { } while (false); do { MOZ_CrashSequence(__null, 931 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 932 | ||||
| 933 | for (size_t slot_id = 0; slot_id < mNumUsedSlots; slot_id++) { | |||
| 934 | MemSlot& slot = mSlots[slot_id]; | |||
| 935 | if (slot.mPtr) { | |||
| 936 | for (size_t i = 0; i < num_dists; i++) { | |||
| 937 | if (slot.mRequest <= dists[i].maxSize()) { | |||
| 938 | dists[i].addRequest(slot.mRequest); | |||
| 939 | break; | |||
| 940 | } | |||
| 941 | } | |||
| 942 | } | |||
| 943 | } | |||
| 944 | ||||
| 945 | for (unsigned i = 0; i < num_dists; i++) { | |||
| 946 | dists[i].printDist(mStdErr); | |||
| 947 | } | |||
| 948 | } | |||
| 949 | ||||
| 950 | #ifdef XP_LINUX1 | |||
| 951 | size_t get_rss() { | |||
| 952 | if (mGetRSSFailed) { | |||
| 953 | return 0; | |||
| 954 | } | |||
| 955 | ||||
| 956 | // On Linux we can determine the RSS of the heap area by examining the | |||
| 957 | // smaps file. | |||
| 958 | mozilla::Maybe<SMapsReader> reader = SMapsReader::open(); | |||
| 959 | if (!reader) { | |||
| 960 | mGetRSSFailed = true; | |||
| 961 | return 0; | |||
| 962 | } | |||
| 963 | ||||
| 964 | size_t rss = 0; | |||
| 965 | while (Maybe<MemoryMap> map = reader->readMap(mStdErr)) { | |||
| 966 | if (map->IsCandidate() && !mSlots.ownsMapping(map->mStart) && | |||
| 967 | !InitialMapsContains(map->mStart)) { | |||
| 968 | rss += map->mRSS; | |||
| 969 | } | |||
| 970 | } | |||
| 971 | ||||
| 972 | return rss; | |||
| 973 | } | |||
| 974 | ||||
| 975 | bool InitialMapsContains(uintptr_t aRangeStart) { | |||
| 976 | for (unsigned i = 0; i < mNumInitialMaps; i++) { | |||
| 977 | MOZ_ASSERT(i < MAX_INITIAL_MAPS)do { static_assert( mozilla::detail::AssertionConditionType< decltype(i < MAX_INITIAL_MAPS)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(i < MAX_INITIAL_MAPS))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("i < MAX_INITIAL_MAPS" , "/root/firefox-clang/memory/replace/logalloc/replay/Replay.cpp" , 977); do { } while (false); do { MOZ_CrashSequence(__null, 977 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); | |||
| 978 | ||||
| 979 | if (mInitialMaps[i] == aRangeStart) { | |||
| 980 | return true; | |||
| 981 | } | |||
| 982 | } | |||
| 983 | return false; | |||
| 984 | } | |||
| 985 | ||||
| 986 | public: | |||
| 987 | void BuildInitialMapInfo() { | |||
| 988 | if (mGetRSSFailed
| |||
| 989 | return; | |||
| 990 | } | |||
| 991 | ||||
| 992 | Maybe<SMapsReader> reader = SMapsReader::open(); | |||
| 993 | if (!reader) { | |||
| 994 | mGetRSSFailed = true; | |||
| 995 | return; | |||
| 996 | } | |||
| 997 | ||||
| 998 | while (Maybe<MemoryMap> map = reader->readMap(mStdErr)) { | |||
| 999 | if (map->IsCandidate()) { | |||
| 1000 | if (mNumInitialMaps >= MAX_INITIAL_MAPS) { | |||
| 1001 | FdPrintf(mStdErr, "Too many initial mappings, can't compute RSS\n"); | |||
| 1002 | mGetRSSFailed = false; | |||
| 1003 | return; | |||
| 1004 | } | |||
| 1005 | ||||
| 1006 | mInitialMaps[mNumInitialMaps++] = map->mStart; | |||
| 1007 | } | |||
| 1008 | } | |||
| 1009 | } | |||
| 1010 | #endif | |||
| 1011 | ||||
| 1012 | private: | |||
| 1013 | MemSlot& SlotForResult(Buffer& aResult) { | |||
| 1014 | /* Parse result value and get the corresponding slot. */ | |||
| 1015 | Buffer dummy = aResult.SplitChar('='); | |||
| 1016 | Buffer dummy2 = aResult.SplitChar('#'); | |||
| 1017 | if (dummy || dummy2) { | |||
| 1018 | die("Malformed input"); | |||
| 1019 | } | |||
| 1020 | ||||
| 1021 | size_t slot_id = parseNumber(aResult); | |||
| 1022 | mNumUsedSlots = std::max(mNumUsedSlots, slot_id + 1); | |||
| 1023 | ||||
| 1024 | return mSlots[slot_id]; | |||
| 1025 | } | |||
| 1026 | ||||
| 1027 | void MaybeCommit(MemSlot& aSlot) { | |||
| 1028 | if (mDoMemset) { | |||
| 1029 | // Write any byte, 0x55 isn't significant. | |||
| 1030 | memset(aSlot.mPtr, 0x55, aSlot.mRequest); | |||
| 1031 | } | |||
| 1032 | } | |||
| 1033 | ||||
| 1034 | platform_handle_t mStdErr; | |||
| 1035 | size_t mOps = 0; | |||
| 1036 | ||||
| 1037 | // The number of slots that have been used. It is used to iterate over slots | |||
| 1038 | // without accessing those we haven't initialised. | |||
| 1039 | size_t mNumUsedSlots = 0; | |||
| 1040 | ||||
| 1041 | MemSlotList mSlots; | |||
| 1042 | size_t mTotalRequestedSize = 0; | |||
| 1043 | size_t mTotalAllocatedSize = 0; | |||
| 1044 | // Whether to calculate slop for all allocations over the runtime of a | |||
| 1045 | // process. | |||
| 1046 | bool mCalculateSlop = false; | |||
| 1047 | bool mDoMemset = false; | |||
| 1048 | ||||
| 1049 | #ifdef XP_LINUX1 | |||
| 1050 | // If we have a failure reading smaps info then this is used to disable that | |||
| 1051 | // feature. | |||
| 1052 | bool mGetRSSFailed = false; | |||
| 1053 | ||||
| 1054 | // The initial memory mappings are recorded here at start up. We exclude | |||
| 1055 | // memory in these mappings when computing RSS. We assume they do not grow | |||
| 1056 | // and that no regions are allocated near them, this is true because they'll | |||
| 1057 | // only record the .bss and .data segments from our binary and shared objects | |||
| 1058 | // or regions that logalloc-replay has created for MappedArrays. | |||
| 1059 | // | |||
| 1060 | // 64 should be enough for anybody. | |||
| 1061 | static constexpr unsigned MAX_INITIAL_MAPS = 64; | |||
| 1062 | uintptr_t mInitialMaps[MAX_INITIAL_MAPS]; | |||
| 1063 | unsigned mNumInitialMaps = 0; | |||
| 1064 | #endif // XP_LINUX | |||
| 1065 | }; | |||
| 1066 | ||||
| 1067 | MOZ_RUNINIT static Replay replay; | |||
| 1068 | ||||
| 1069 | int main(int argc, const char* argv[]) { | |||
| 1070 | size_t first_pid = 0; | |||
| 1071 | FdReader reader(0); | |||
| 1072 | ||||
| 1073 | for (int i = 1; i < argc; i++) { | |||
| 1074 | const char* option = argv[i]; | |||
| 1075 | if (strcmp(option, "-s") == 0) { | |||
| 1076 | // Do accounting to calculate allocation slop. | |||
| 1077 | replay.enableSlopCalculation(); | |||
| 1078 | } else if (strcmp(option, "-c") == 0) { | |||
| 1079 | // Touch memory as we allocate it. | |||
| 1080 | replay.enableMemset(); | |||
| 1081 | } else { | |||
| 1082 | fprintf(stderrstderr, "Unknown command line option: %s\n", option); | |||
| 1083 | return EXIT_FAILURE1; | |||
| 1084 | } | |||
| 1085 | } | |||
| 1086 | ||||
| 1087 | /* Read log from stdin and dispatch function calls to the Replay instance. | |||
| 1088 | * The log format is essentially: | |||
| 1089 | * <pid> <tid> <function>([<args>])[=<result>] | |||
| 1090 | * <args> is a comma separated list of arguments. | |||
| 1091 | * | |||
| 1092 | * The logs are expected to be preprocessed so that allocations are | |||
| 1093 | * attributed a tracking slot. The input is trusted not to have crazy | |||
| 1094 | * values for these slot numbers. | |||
| 1095 | * | |||
| 1096 | * <result>, as well as some of the args to some of the function calls are | |||
| 1097 | * such slot numbers. | |||
| 1098 | */ | |||
| 1099 | while (true) { | |||
| 1100 | Buffer line = reader.ReadLine(); | |||
| 1101 | ||||
| 1102 | if (!line) { | |||
| 1103 | break; | |||
| 1104 | } | |||
| 1105 | ||||
| 1106 | size_t pid = parseNumber(line.SplitChar(' ')); | |||
| 1107 | if (!first_pid) { | |||
| 1108 | first_pid = pid; | |||
| 1109 | } | |||
| 1110 | ||||
| 1111 | /* The log may contain data for several processes, only entries for the | |||
| 1112 | * very first that appears are treated. */ | |||
| 1113 | if (first_pid != pid) { | |||
| 1114 | continue; | |||
| 1115 | } | |||
| 1116 | ||||
| 1117 | /* The log contains thread ids for manual analysis, but we just ignore them | |||
| 1118 | * for now. */ | |||
| 1119 | parseNumber(line.SplitChar(' ')); | |||
| 1120 | ||||
| 1121 | Buffer func = line.SplitChar('('); | |||
| 1122 | Buffer args = line.SplitChar(')'); | |||
| 1123 | ||||
| 1124 | if (func == Buffer("jemalloc_stats")) { | |||
| 1125 | replay.jemalloc_stats(args, line); | |||
| 1126 | } else if (func == Buffer("free")) { | |||
| 1127 | replay.free(args, line); | |||
| 1128 | } else if (func == Buffer("malloc")) { | |||
| 1129 | replay.malloc(args, line); | |||
| 1130 | } else if (func == Buffer("posix_memalign")) { | |||
| 1131 | replay.posix_memalign(args, line); | |||
| 1132 | } else if (func == Buffer("aligned_alloc")) { | |||
| 1133 | replay.aligned_alloc(args, line); | |||
| 1134 | } else if (func == Buffer("calloc")) { | |||
| 1135 | replay.calloc(args, line); | |||
| 1136 | } else if (func == Buffer("realloc")) { | |||
| 1137 | replay.realloc(args, line); | |||
| 1138 | } else if (func == Buffer("memalign")) { | |||
| 1139 | replay.memalign(args, line); | |||
| 1140 | } else if (func == Buffer("valloc")) { | |||
| 1141 | replay.valloc(args, line); | |||
| 1142 | } else { | |||
| 1143 | die("Malformed input"); | |||
| 1144 | } | |||
| 1145 | } | |||
| 1146 | ||||
| 1147 | return 0; | |||
| 1148 | } |