1//===-- sanitizer_allocator_combined.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// This class implements a complete memory allocator by using two
17// internal allocators:
18// PrimaryAllocator is efficient, but may not allocate some sizes (alignments).
19//  When allocating 2^x bytes it should return 2^x aligned chunk.
20// PrimaryAllocator is used via a local AllocatorCache.
21// SecondaryAllocator can allocate anything, but is not efficient.
22template <class PrimaryAllocator,
23          class LargeMmapAllocatorPtrArray = DefaultLargeMmapAllocatorPtrArray>
24class CombinedAllocator {
25 public:
26  using AllocatorCache = typename PrimaryAllocator::AllocatorCache;
27  using SecondaryAllocator =
28      LargeMmapAllocator<typename PrimaryAllocator::MapUnmapCallback,
29                         LargeMmapAllocatorPtrArray,
30                         typename PrimaryAllocator::AddressSpaceView>;
31
32  void InitLinkerInitialized(s32 release_to_os_interval_ms) {
33    stats_.InitLinkerInitialized();
34    primary_.Init(release_to_os_interval_ms);
35    secondary_.InitLinkerInitialized();
36  }
37
38  void Init(s32 release_to_os_interval_ms, uptr heap_start = 0) {
39    stats_.Init();
40    primary_.Init(release_to_os_interval_ms, heap_start);
41    secondary_.Init();
42  }
43
44  void *Allocate(AllocatorCache *cache, uptr size, uptr alignment) {
45    // Returning 0 on malloc(0) may break a lot of code.
46    if (size == 0)
47      size = 1;
48    if (size + alignment < size) {
49      Report("WARNING: %s: CombinedAllocator allocation overflow: "
50             "0x%zx bytes with 0x%zx alignment requested\n",
51             SanitizerToolName, size, alignment);
52      return nullptr;
53    }
54    uptr original_size = size;
55    // If alignment requirements are to be fulfilled by the frontend allocator
56    // rather than by the primary or secondary, passing an alignment lower than
57    // or equal to 8 will prevent any further rounding up, as well as the later
58    // alignment check.
59    if (alignment > 8)
60      size = RoundUpTo(size, alignment);
61    // The primary allocator should return a 2^x aligned allocation when
62    // requested 2^x bytes, hence using the rounded up 'size' when being
63    // serviced by the primary (this is no longer true when the primary is
64    // using a non-fixed base address). The secondary takes care of the
65    // alignment without such requirement, and allocating 'size' would use
66    // extraneous memory, so we employ 'original_size'.
67    void *res;
68    if (primary_.CanAllocate(size, alignment))
69      res = cache->Allocate(&primary_, primary_.ClassID(size));
70    else
71      res = secondary_.Allocate(&stats_, original_size, alignment);
72    if (alignment > 8)
73      CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0);
74    return res;
75  }
76
77  s32 ReleaseToOSIntervalMs() const {
78    return primary_.ReleaseToOSIntervalMs();
79  }
80
81  void SetReleaseToOSIntervalMs(s32 release_to_os_interval_ms) {
82    primary_.SetReleaseToOSIntervalMs(release_to_os_interval_ms);
83  }
84
85  void ForceReleaseToOS() {
86    primary_.ForceReleaseToOS();
87  }
88
89  void Deallocate(AllocatorCache *cache, void *p) {
90    if (!p) return;
91    if (primary_.PointerIsMine(p))
92      cache->Deallocate(&primary_, primary_.GetSizeClass(p), p);
93    else
94      secondary_.Deallocate(&stats_, p);
95  }
96
97  void *Reallocate(AllocatorCache *cache, void *p, uptr new_size,
98                   uptr alignment) {
99    if (!p)
100      return Allocate(cache, new_size, alignment);
101    if (!new_size) {
102      Deallocate(cache, p);
103      return nullptr;
104    }
105    CHECK(PointerIsMine(p));
106    uptr old_size = GetActuallyAllocatedSize(p);
107    uptr memcpy_size = Min(new_size, old_size);
108    void *new_p = Allocate(cache, new_size, alignment);
109    if (new_p)
110      internal_memcpy(new_p, p, memcpy_size);
111    Deallocate(cache, p);
112    return new_p;
113  }
114
115  bool PointerIsMine(const void *p) const {
116    if (primary_.PointerIsMine(p))
117      return true;
118    return secondary_.PointerIsMine(p);
119  }
120
121  bool FromPrimary(const void *p) const { return primary_.PointerIsMine(p); }
122
123  void *GetMetaData(const void *p) {
124    if (primary_.PointerIsMine(p))
125      return primary_.GetMetaData(p);
126    return secondary_.GetMetaData(p);
127  }
128
129  void *GetBlockBegin(const void *p) {
130    if (primary_.PointerIsMine(p))
131      return primary_.GetBlockBegin(p);
132    return secondary_.GetBlockBegin(p);
133  }
134
135  // This function does the same as GetBlockBegin, but is much faster.
136  // Must be called with the allocator locked.
137  void *GetBlockBeginFastLocked(const void *p) {
138    if (primary_.PointerIsMine(p))
139      return primary_.GetBlockBegin(p);
140    return secondary_.GetBlockBeginFastLocked(p);
141  }
142
143  uptr GetActuallyAllocatedSize(void *p) {
144    if (primary_.PointerIsMine(p))
145      return primary_.GetActuallyAllocatedSize(p);
146    return secondary_.GetActuallyAllocatedSize(p);
147  }
148
149  uptr TotalMemoryUsed() {
150    return primary_.TotalMemoryUsed() + secondary_.TotalMemoryUsed();
151  }
152
153  void TestOnlyUnmap() { primary_.TestOnlyUnmap(); }
154
155  void InitCache(AllocatorCache *cache) {
156    cache->Init(&stats_);
157  }
158
159  void DestroyCache(AllocatorCache *cache) {
160    cache->Destroy(&primary_, &stats_);
161  }
162
163  void SwallowCache(AllocatorCache *cache) {
164    cache->Drain(&primary_);
165  }
166
167  void GetStats(AllocatorStatCounters s) const {
168    stats_.Get(s);
169  }
170
171  void PrintStats() {
172    primary_.PrintStats();
173    secondary_.PrintStats();
174  }
175
176  // ForceLock() and ForceUnlock() are needed to implement Darwin malloc zone
177  // introspection API.
178  void ForceLock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
179    primary_.ForceLock();
180    secondary_.ForceLock();
181  }
182
183  void ForceUnlock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
184    secondary_.ForceUnlock();
185    primary_.ForceUnlock();
186  }
187
188  // Iterate over all existing chunks.
189  // The allocator must be locked when calling this function.
190  void ForEachChunk(ForEachChunkCallback callback, void *arg) {
191    primary_.ForEachChunk(callback, arg);
192    secondary_.ForEachChunk(callback, arg);
193  }
194
195 private:
196  PrimaryAllocator primary_;
197  SecondaryAllocator secondary_;
198  AllocatorGlobalStats stats_;
199};
200