| 1 | //===-- sanitizer_allocator_primary32.h -------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Part of the Sanitizer Allocator. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | #ifndef SANITIZER_ALLOCATOR_H |
| 13 | #error This file must be included inside sanitizer_allocator.h |
| 14 | #endif |
| 15 | |
| 16 | template<class SizeClassAllocator> struct SizeClassAllocator32LocalCache; |
| 17 | |
| 18 | // SizeClassAllocator32 -- allocator for 32-bit address space. |
| 19 | // This allocator can theoretically be used on 64-bit arch, but there it is less |
| 20 | // efficient than SizeClassAllocator64. |
| 21 | // |
| 22 | // [kSpaceBeg, kSpaceBeg + kSpaceSize) is the range of addresses which can |
| 23 | // be returned by MmapOrDie(). |
| 24 | // |
| 25 | // Region: |
| 26 | // a result of a single call to MmapAlignedOrDieOnFatalError(kRegionSize, |
| 27 | // kRegionSize). |
| 28 | // Since the regions are aligned by kRegionSize, there are exactly |
| 29 | // kNumPossibleRegions possible regions in the address space and so we keep |
| 30 | // a ByteMap possible_regions to store the size classes of each Region. |
| 31 | // 0 size class means the region is not used by the allocator. |
| 32 | // |
| 33 | // One Region is used to allocate chunks of a single size class. |
| 34 | // A Region looks like this: |
| 35 | // UserChunk1 .. UserChunkN <gap> MetaChunkN .. MetaChunk1 |
| 36 | // |
| 37 | // In order to avoid false sharing the objects of this class should be |
| 38 | // chache-line aligned. |
| 39 | |
| 40 | struct SizeClassAllocator32FlagMasks { // Bit masks. |
| 41 | enum { |
| 42 | kRandomShuffleChunks = 1, |
| 43 | kUseSeparateSizeClassForBatch = 2, |
| 44 | }; |
| 45 | }; |
| 46 | |
| 47 | template <class Params> |
| 48 | class SizeClassAllocator32 { |
| 49 | private: |
| 50 | static const u64 kTwoLevelByteMapSize1 = |
| 51 | (Params::kSpaceSize >> Params::kRegionSizeLog) >> 12; |
| 52 | static const u64 kMinFirstMapSizeTwoLevelByteMap = 4; |
| 53 | |
| 54 | public: |
| 55 | using AddressSpaceView = typename Params::AddressSpaceView; |
| 56 | static const uptr kSpaceBeg = Params::kSpaceBeg; |
| 57 | static const u64 kSpaceSize = Params::kSpaceSize; |
| 58 | static const uptr kMetadataSize = Params::kMetadataSize; |
| 59 | typedef typename Params::SizeClassMap SizeClassMap; |
| 60 | static const uptr kRegionSizeLog = Params::kRegionSizeLog; |
| 61 | typedef typename Params::MapUnmapCallback MapUnmapCallback; |
| 62 | using ByteMap = typename conditional< |
| 63 | (kTwoLevelByteMapSize1 < kMinFirstMapSizeTwoLevelByteMap), |
| 64 | FlatByteMap<(Params::kSpaceSize >> Params::kRegionSizeLog), |
| 65 | AddressSpaceView>, |
| 66 | TwoLevelByteMap<kTwoLevelByteMapSize1, 1 << 12, AddressSpaceView>>::type; |
| 67 | |
| 68 | COMPILER_CHECK(!SANITIZER_SIGN_EXTENDED_ADDRESSES || |
| 69 | (kSpaceSize & (kSpaceSize - 1)) == 0); |
| 70 | |
| 71 | static const bool kRandomShuffleChunks = Params::kFlags & |
| 72 | SizeClassAllocator32FlagMasks::kRandomShuffleChunks; |
| 73 | static const bool kUseSeparateSizeClassForBatch = Params::kFlags & |
| 74 | SizeClassAllocator32FlagMasks::kUseSeparateSizeClassForBatch; |
| 75 | |
| 76 | struct TransferBatch { |
| 77 | static const uptr kMaxNumCached = SizeClassMap::kMaxNumCachedHint - 2; |
| 78 | void SetFromArray(void *batch[], uptr count) { |
| 79 | DCHECK_LE(count, kMaxNumCached); |
| 80 | count_ = count; |
| 81 | for (uptr i = 0; i < count; i++) |
| 82 | batch_[i] = batch[i]; |
| 83 | } |
| 84 | uptr Count() const { return count_; } |
| 85 | void Clear() { count_ = 0; } |
| 86 | void Add(void *ptr) { |
| 87 | batch_[count_++] = ptr; |
| 88 | DCHECK_LE(count_, kMaxNumCached); |
| 89 | } |
| 90 | void CopyToArray(void *to_batch[]) const { |
| 91 | for (uptr i = 0, n = Count(); i < n; i++) |
| 92 | to_batch[i] = batch_[i]; |
| 93 | } |
| 94 | |
| 95 | // How much memory do we need for a batch containing n elements. |
| 96 | static uptr AllocationSizeRequiredForNElements(uptr n) { |
| 97 | return sizeof(uptr) * 2 + sizeof(void *) * n; |
| 98 | } |
| 99 | static uptr MaxCached(uptr size) { |
| 100 | return Min(kMaxNumCached, SizeClassMap::MaxCachedHint(size)); |
| 101 | } |
| 102 | |
| 103 | TransferBatch *next; |
| 104 | |
| 105 | private: |
| 106 | uptr count_; |
| 107 | void *batch_[kMaxNumCached]; |
| 108 | }; |
| 109 | |
| 110 | static const uptr kBatchSize = sizeof(TransferBatch); |
| 111 | COMPILER_CHECK((kBatchSize & (kBatchSize - 1)) == 0); |
| 112 | COMPILER_CHECK(kBatchSize == SizeClassMap::kMaxNumCachedHint * sizeof(uptr)); |
| 113 | |
| 114 | static uptr ClassIdToSize(uptr class_id) { |
| 115 | return (class_id == SizeClassMap::kBatchClassID) ? |
| 116 | kBatchSize : SizeClassMap::Size(class_id); |
| 117 | } |
| 118 | |
| 119 | typedef SizeClassAllocator32<Params> ThisT; |
| 120 | typedef SizeClassAllocator32LocalCache<ThisT> AllocatorCache; |
| 121 | |
| 122 | void Init(s32 release_to_os_interval_ms, uptr heap_start = 0) { |
| 123 | CHECK(!heap_start); |
| 124 | possible_regions.Init(); |
| 125 | internal_memset(size_class_info_array, 0, sizeof(size_class_info_array)); |
| 126 | } |
| 127 | |
| 128 | s32 ReleaseToOSIntervalMs() const { |
| 129 | return kReleaseToOSIntervalNever; |
| 130 | } |
| 131 | |
| 132 | void SetReleaseToOSIntervalMs(s32 release_to_os_interval_ms) { |
| 133 | // This is empty here. Currently only implemented in 64-bit allocator. |
| 134 | } |
| 135 | |
| 136 | void ForceReleaseToOS() { |
| 137 | // Currently implemented in 64-bit allocator only. |
| 138 | } |
| 139 | |
| 140 | void *MapWithCallback(uptr size) { |
| 141 | void *res = MmapOrDie(size, mem_type: PrimaryAllocatorName); |
| 142 | MapUnmapCallback().OnMap((uptr)res, size); |
| 143 | return res; |
| 144 | } |
| 145 | |
| 146 | void UnmapWithCallback(uptr beg, uptr size) { |
| 147 | MapUnmapCallback().OnUnmap(beg, size); |
| 148 | UnmapOrDie(addr: reinterpret_cast<void *>(beg), size); |
| 149 | } |
| 150 | |
| 151 | static bool CanAllocate(uptr size, uptr alignment) { |
| 152 | return size <= SizeClassMap::kMaxSize && |
| 153 | alignment <= SizeClassMap::kMaxSize; |
| 154 | } |
| 155 | |
| 156 | void *GetMetaData(const void *p) { |
| 157 | CHECK(kMetadataSize); |
| 158 | CHECK(PointerIsMine(p)); |
| 159 | uptr mem = reinterpret_cast<uptr>(p); |
| 160 | uptr beg = ComputeRegionBeg(mem); |
| 161 | uptr size = ClassIdToSize(class_id: GetSizeClass(p)); |
| 162 | u32 offset = mem - beg; |
| 163 | uptr n = offset / (u32)size; // 32-bit division |
| 164 | uptr meta = (beg + kRegionSize) - (n + 1) * kMetadataSize; |
| 165 | return reinterpret_cast<void*>(meta); |
| 166 | } |
| 167 | |
| 168 | NOINLINE TransferBatch *AllocateBatch(AllocatorStats *stat, AllocatorCache *c, |
| 169 | uptr class_id) { |
| 170 | DCHECK_LT(class_id, kNumClasses); |
| 171 | SizeClassInfo *sci = GetSizeClassInfo(class_id); |
| 172 | SpinMutexLock l(&sci->mutex); |
| 173 | if (sci->free_list.empty()) { |
| 174 | if (UNLIKELY(!PopulateFreeList(stat, c, sci, class_id))) |
| 175 | return nullptr; |
| 176 | DCHECK(!sci->free_list.empty()); |
| 177 | } |
| 178 | TransferBatch *b = sci->free_list.front(); |
| 179 | sci->free_list.pop_front(); |
| 180 | return b; |
| 181 | } |
| 182 | |
| 183 | NOINLINE void DeallocateBatch(AllocatorStats *stat, uptr class_id, |
| 184 | TransferBatch *b) { |
| 185 | DCHECK_LT(class_id, kNumClasses); |
| 186 | CHECK_GT(b->Count(), 0); |
| 187 | SizeClassInfo *sci = GetSizeClassInfo(class_id); |
| 188 | SpinMutexLock l(&sci->mutex); |
| 189 | sci->free_list.push_front(b); |
| 190 | } |
| 191 | |
| 192 | bool PointerIsMine(const void *p) const { |
| 193 | uptr mem = reinterpret_cast<uptr>(p); |
| 194 | if (SANITIZER_SIGN_EXTENDED_ADDRESSES) |
| 195 | mem &= (kSpaceSize - 1); |
| 196 | if (mem < kSpaceBeg || mem >= kSpaceBeg + kSpaceSize) |
| 197 | return false; |
| 198 | return GetSizeClass(p) != 0; |
| 199 | } |
| 200 | |
| 201 | uptr GetSizeClass(const void *p) const { |
| 202 | uptr id = ComputeRegionId(mem: reinterpret_cast<uptr>(p)); |
| 203 | return possible_regions.contains(id) ? possible_regions[id] : 0; |
| 204 | } |
| 205 | |
| 206 | void *GetBlockBegin(const void *p) { |
| 207 | CHECK(PointerIsMine(p)); |
| 208 | uptr mem = reinterpret_cast<uptr>(p); |
| 209 | uptr beg = ComputeRegionBeg(mem); |
| 210 | uptr size = ClassIdToSize(class_id: GetSizeClass(p)); |
| 211 | u32 offset = mem - beg; |
| 212 | u32 n = offset / (u32)size; // 32-bit division |
| 213 | uptr res = beg + (n * (u32)size); |
| 214 | return reinterpret_cast<void*>(res); |
| 215 | } |
| 216 | |
| 217 | uptr GetActuallyAllocatedSize(void *p) { |
| 218 | CHECK(PointerIsMine(p)); |
| 219 | return ClassIdToSize(class_id: GetSizeClass(p)); |
| 220 | } |
| 221 | |
| 222 | static uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); } |
| 223 | |
| 224 | uptr TotalMemoryUsed() { |
| 225 | // No need to lock here. |
| 226 | uptr res = 0; |
| 227 | for (uptr i = 0; i < kNumPossibleRegions; i++) |
| 228 | if (possible_regions[i]) |
| 229 | res += kRegionSize; |
| 230 | return res; |
| 231 | } |
| 232 | |
| 233 | void TestOnlyUnmap() { |
| 234 | for (uptr i = 0; i < kNumPossibleRegions; i++) |
| 235 | if (possible_regions[i]) |
| 236 | UnmapWithCallback(beg: (i * kRegionSize), size: kRegionSize); |
| 237 | } |
| 238 | |
| 239 | // ForceLock() and ForceUnlock() are needed to implement Darwin malloc zone |
| 240 | // introspection API. |
| 241 | void ForceLock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 242 | for (uptr i = 0; i < kNumClasses; i++) { |
| 243 | GetSizeClassInfo(class_id: i)->mutex.Lock(); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | void ForceUnlock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS { |
| 248 | for (int i = kNumClasses - 1; i >= 0; i--) { |
| 249 | GetSizeClassInfo(class_id: i)->mutex.Unlock(); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | // Iterate over all existing chunks. |
| 254 | // The allocator must be locked when calling this function. |
| 255 | void ForEachChunk(ForEachChunkCallback callback, void *arg) const { |
| 256 | for (uptr region = 0; region < kNumPossibleRegions; region++) |
| 257 | if (possible_regions.contains(region) && possible_regions[region]) { |
| 258 | uptr chunk_size = ClassIdToSize(class_id: possible_regions[region]); |
| 259 | uptr max_chunks_in_region = kRegionSize / (chunk_size + kMetadataSize); |
| 260 | uptr region_beg = region * kRegionSize; |
| 261 | for (uptr chunk = region_beg; |
| 262 | chunk < region_beg + max_chunks_in_region * chunk_size; |
| 263 | chunk += chunk_size) { |
| 264 | // Too slow: CHECK_EQ((void *)chunk, GetBlockBegin((void *)chunk)); |
| 265 | callback(chunk, arg); |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | void PrintStats() {} |
| 271 | |
| 272 | static uptr AdditionalSize() { return 0; } |
| 273 | |
| 274 | typedef SizeClassMap SizeClassMapT; |
| 275 | static const uptr kNumClasses = SizeClassMap::kNumClasses; |
| 276 | |
| 277 | private: |
| 278 | static const uptr kRegionSize = 1 << kRegionSizeLog; |
| 279 | static const uptr kNumPossibleRegions = kSpaceSize / kRegionSize; |
| 280 | |
| 281 | struct alignas(SANITIZER_CACHE_LINE_SIZE) SizeClassInfo { |
| 282 | StaticSpinMutex mutex; |
| 283 | IntrusiveList<TransferBatch> free_list; |
| 284 | u32 rand_state; |
| 285 | }; |
| 286 | COMPILER_CHECK(sizeof(SizeClassInfo) % kCacheLineSize == 0); |
| 287 | |
| 288 | uptr ComputeRegionId(uptr mem) const { |
| 289 | if (SANITIZER_SIGN_EXTENDED_ADDRESSES) |
| 290 | mem &= (kSpaceSize - 1); |
| 291 | const uptr res = mem >> kRegionSizeLog; |
| 292 | CHECK_LT(res, kNumPossibleRegions); |
| 293 | return res; |
| 294 | } |
| 295 | |
| 296 | uptr ComputeRegionBeg(uptr mem) const { return mem & ~(kRegionSize - 1); } |
| 297 | |
| 298 | uptr AllocateRegion(AllocatorStats *stat, uptr class_id) { |
| 299 | DCHECK_LT(class_id, kNumClasses); |
| 300 | const uptr res = reinterpret_cast<uptr>(MmapAlignedOrDieOnFatalError( |
| 301 | size: kRegionSize, alignment: kRegionSize, mem_type: PrimaryAllocatorName)); |
| 302 | if (UNLIKELY(!res)) |
| 303 | return 0; |
| 304 | MapUnmapCallback().OnMap(res, kRegionSize); |
| 305 | stat->Add(i: AllocatorStatMapped, v: kRegionSize); |
| 306 | CHECK(IsAligned(res, kRegionSize)); |
| 307 | possible_regions[ComputeRegionId(mem: res)] = class_id; |
| 308 | return res; |
| 309 | } |
| 310 | |
| 311 | SizeClassInfo *GetSizeClassInfo(uptr class_id) { |
| 312 | DCHECK_LT(class_id, kNumClasses); |
| 313 | return &size_class_info_array[class_id]; |
| 314 | } |
| 315 | |
| 316 | bool PopulateBatches(AllocatorCache *c, SizeClassInfo *sci, uptr class_id, |
| 317 | TransferBatch **current_batch, uptr max_count, |
| 318 | uptr *pointers_array, uptr count) { |
| 319 | // If using a separate class for batches, we do not need to shuffle it. |
| 320 | if (kRandomShuffleChunks && (!kUseSeparateSizeClassForBatch || |
| 321 | class_id != SizeClassMap::kBatchClassID)) |
| 322 | RandomShuffle(pointers_array, count, &sci->rand_state); |
| 323 | TransferBatch *b = *current_batch; |
| 324 | for (uptr i = 0; i < count; i++) { |
| 325 | if (!b) { |
| 326 | b = c->CreateBatch(class_id, this, (TransferBatch*)pointers_array[i]); |
| 327 | if (UNLIKELY(!b)) |
| 328 | return false; |
| 329 | b->Clear(); |
| 330 | } |
| 331 | b->Add((void*)pointers_array[i]); |
| 332 | if (b->Count() == max_count) { |
| 333 | sci->free_list.push_back(b); |
| 334 | b = nullptr; |
| 335 | } |
| 336 | } |
| 337 | *current_batch = b; |
| 338 | return true; |
| 339 | } |
| 340 | |
| 341 | bool PopulateFreeList(AllocatorStats *stat, AllocatorCache *c, |
| 342 | SizeClassInfo *sci, uptr class_id) { |
| 343 | const uptr region = AllocateRegion(stat, class_id); |
| 344 | if (UNLIKELY(!region)) |
| 345 | return false; |
| 346 | if (kRandomShuffleChunks) |
| 347 | if (UNLIKELY(sci->rand_state == 0)) |
| 348 | // The random state is initialized from ASLR (PIE) and time. |
| 349 | sci->rand_state = reinterpret_cast<uptr>(sci) ^ NanoTime(); |
| 350 | const uptr size = ClassIdToSize(class_id); |
| 351 | const uptr n_chunks = kRegionSize / (size + kMetadataSize); |
| 352 | const uptr max_count = TransferBatch::MaxCached(size); |
| 353 | DCHECK_GT(max_count, 0); |
| 354 | TransferBatch *b = nullptr; |
| 355 | constexpr uptr kShuffleArraySize = 48; |
| 356 | UNINITIALIZED uptr shuffle_array[kShuffleArraySize]; |
| 357 | uptr count = 0; |
| 358 | for (uptr i = region; i < region + n_chunks * size; i += size) { |
| 359 | shuffle_array[count++] = i; |
| 360 | if (count == kShuffleArraySize) { |
| 361 | if (UNLIKELY(!PopulateBatches(c, sci, class_id, &b, max_count, |
| 362 | shuffle_array, count))) |
| 363 | return false; |
| 364 | count = 0; |
| 365 | } |
| 366 | } |
| 367 | if (count) { |
| 368 | if (UNLIKELY(!PopulateBatches(c, sci, class_id, &b, max_count, |
| 369 | shuffle_array, count))) |
| 370 | return false; |
| 371 | } |
| 372 | if (b) { |
| 373 | CHECK_GT(b->Count(), 0); |
| 374 | sci->free_list.push_back(b); |
| 375 | } |
| 376 | return true; |
| 377 | } |
| 378 | |
| 379 | ByteMap possible_regions; |
| 380 | SizeClassInfo size_class_info_array[kNumClasses]; |
| 381 | }; |
| 382 | |