xref: /aosp_15_r20/art/runtime/base/gc_visited_arena_pool.h (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
18 #define ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
19 
20 #include <set>
21 
22 #include "base/allocator.h"
23 #include "base/arena_allocator.h"
24 #include "base/casts.h"
25 #include "base/hash_set.h"
26 #include "base/locks.h"
27 #include "base/mem_map.h"
28 #include "read_barrier_config.h"
29 #include "runtime.h"
30 
31 namespace art HIDDEN {
32 
33 // GcVisitedArenaPool can be used for tracking allocations so that they can
34 // be visited during GC to update the GC-roots inside them.
35 
36 // An Arena which tracks its allocations.
37 class TrackedArena final : public Arena {
38  public:
39   // Used for searching in maps. Only arena's starting address is relevant.
TrackedArena(uint8_t * addr)40   explicit TrackedArena(uint8_t* addr) : pre_zygote_fork_(false) { memory_ = addr; }
41   TrackedArena(uint8_t* start, size_t size, bool pre_zygote_fork, bool single_obj_arena);
42 
43   template <typename PageVisitor>
VisitRoots(PageVisitor & visitor)44   void VisitRoots(PageVisitor& visitor) const REQUIRES_SHARED(Locks::mutator_lock_) {
45     uint8_t* page_begin = Begin();
46     if (first_obj_array_.get() != nullptr) {
47       DCHECK_ALIGNED_PARAM(Size(), gPageSize);
48       DCHECK_ALIGNED_PARAM(Begin(), gPageSize);
49       for (int i = 0, nr_pages = DivideByPageSize(Size());
50            i < nr_pages;
51            i++, page_begin += gPageSize) {
52         uint8_t* first = first_obj_array_[i];
53         if (first != nullptr) {
54           visitor(page_begin, first, gPageSize);
55         } else {
56           break;
57         }
58       }
59     } else {
60       size_t page_size = Size();
61       while (page_size > gPageSize) {
62         visitor(page_begin, nullptr, gPageSize);
63         page_begin += gPageSize;
64         page_size -= gPageSize;
65       }
66       visitor(page_begin, nullptr, page_size);
67     }
68   }
69 
70   // Return the page addr of the first page with first_obj set to nullptr.
GetLastUsedByte()71   uint8_t* GetLastUsedByte() const REQUIRES_SHARED(Locks::mutator_lock_) {
72     // Jump past bytes-allocated for arenas which are not currently being used
73     // by arena-allocator. This helps in reducing loop iterations below.
74     uint8_t* last_byte = AlignUp(Begin() + GetBytesAllocated(), gPageSize);
75     if (first_obj_array_.get() != nullptr) {
76       DCHECK_ALIGNED_PARAM(Begin(), gPageSize);
77       DCHECK_ALIGNED_PARAM(End(), gPageSize);
78       DCHECK_LE(last_byte, End());
79     } else {
80       DCHECK_EQ(last_byte, End());
81     }
82     for (size_t i = DivideByPageSize(last_byte - Begin());
83          last_byte < End() && first_obj_array_[i] != nullptr;
84          last_byte += gPageSize, i++) {
85       // No body.
86     }
87     return last_byte;
88   }
89 
GetFirstObject(uint8_t * addr)90   uint8_t* GetFirstObject(uint8_t* addr) const REQUIRES_SHARED(Locks::mutator_lock_) {
91     DCHECK_LE(Begin(), addr);
92     DCHECK_GT(End(), addr);
93     if (first_obj_array_.get() != nullptr) {
94       return first_obj_array_[DivideByPageSize(addr - Begin())];
95     } else {
96       // The pages of this arena contain array of GC-roots. So we don't need
97       // first-object of any given page of the arena.
98       // Returning null helps distinguish which visitor is to be called.
99       return nullptr;
100     }
101   }
102 
103   // Set 'obj_begin' in first_obj_array_ in every element for which it's the
104   // first object.
105   EXPORT void SetFirstObject(uint8_t* obj_begin, uint8_t* obj_end);
106   // Setup the arena for deferred deletion.
SetupForDeferredDeletion(TrackedArena * next_arena)107   void SetupForDeferredDeletion(TrackedArena* next_arena) {
108     DCHECK(next_arena == nullptr || next_arena->waiting_for_deletion_);
109     DCHECK(!waiting_for_deletion_);
110     waiting_for_deletion_ = true;
111     next_ = next_arena;
112   }
IsWaitingForDeletion()113   bool IsWaitingForDeletion() const { return waiting_for_deletion_; }
114 
115   void Release() override;
IsPreZygoteForkArena()116   bool IsPreZygoteForkArena() const { return pre_zygote_fork_; }
IsSingleObjectArena()117   bool IsSingleObjectArena() const { return first_obj_array_.get() == nullptr; }
118 
119  private:
120   // first_obj_array_[i] is the object that overlaps with the ith page's
121   // beginning, i.e. first_obj_array_[i] <= ith page_begin.
122   std::unique_ptr<uint8_t*[]> first_obj_array_;
123   const bool pre_zygote_fork_;
124   bool waiting_for_deletion_;
125 };
126 
127 // An arena-pool wherein allocations can be tracked so that the GC can visit all
128 // the GC roots. All the arenas are allocated in one sufficiently large memory
129 // range to avoid multiple calls to mremapped/mprotected syscalls.
130 class GcVisitedArenaPool final : public ArenaPool {
131  public:
132 #if defined(__LP64__)
133   // Use a size in multiples of 1GB as that can utilize the optimized mremap
134   // page-table move.
135   static constexpr size_t kLinearAllocPoolSize = 1 * GB;
136   static constexpr size_t kLow4GBLinearAllocPoolSize = 32 * MB;
137 #else
138   static constexpr size_t kLinearAllocPoolSize = 32 * MB;
139 #endif
140 
141   explicit GcVisitedArenaPool(bool low_4gb = false,
142                               bool is_zygote = false,
143                               const char* name = "LinearAlloc");
144   virtual ~GcVisitedArenaPool();
145 
146   Arena* AllocArena(size_t size, bool need_first_obj_arr) REQUIRES(lock_);
147   // Use by arena allocator.
AllocArena(size_t size)148   Arena* AllocArena(size_t size) override REQUIRES(!lock_) {
149     WriterMutexLock wmu(Thread::Current(), lock_);
150     return AllocArena(size, /*need_first_obj_arr=*/false);
151   }
152   void FreeArenaChain(Arena* first) override REQUIRES(!lock_);
153   size_t GetBytesAllocated() const override REQUIRES(!lock_);
ReclaimMemory()154   void ReclaimMemory() override {}
LockReclaimMemory()155   void LockReclaimMemory() override {}
TrimMaps()156   void TrimMaps() override {}
157 
158   EXPORT uint8_t* AllocSingleObjArena(size_t size) REQUIRES(!lock_);
159   EXPORT void FreeSingleObjArena(uint8_t* addr) REQUIRES(!lock_);
160 
Contains(void * ptr)161   bool Contains(void* ptr) REQUIRES(!lock_) {
162     ReaderMutexLock rmu(Thread::Current(), lock_);
163     for (auto& map : maps_) {
164       if (map.HasAddress(ptr)) {
165         return true;
166       }
167     }
168     return false;
169   }
170 
171   template <typename PageVisitor>
VisitRoots(PageVisitor & visitor)172   void VisitRoots(PageVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_, lock_) {
173     for (auto& arena : allocated_arenas_) {
174       arena->VisitRoots(visitor);
175     }
176   }
177 
178   template <typename Callback>
ForEachAllocatedArena(Callback cb)179   void ForEachAllocatedArena(Callback cb) REQUIRES_SHARED(Locks::mutator_lock_, lock_) {
180     // We should not have any unused arenas when calling this function.
181     CHECK(unused_arenas_ == nullptr);
182     for (auto& arena : allocated_arenas_) {
183       cb(*arena);
184     }
185   }
186 
187   // Called in Heap::PreZygoteFork(). All allocations after this are done in
188   // arena-pool which is visited by userfaultfd.
SetupPostZygoteMode()189   void SetupPostZygoteMode() REQUIRES(!lock_) {
190     WriterMutexLock wmu(Thread::Current(), lock_);
191     DCHECK(pre_zygote_fork_);
192     pre_zygote_fork_ = false;
193   }
194 
195   // For userfaultfd GC to be able to acquire the lock to avoid concurrent
196   // release of arenas when it is visiting them.
GetLock()197   ReaderWriterMutex& GetLock() const RETURN_CAPABILITY(lock_) { return lock_; }
198 
199   // Called in the compaction pause to indicate that all arenas that will be
200   // freed until compaction is done shouldn't delete the TrackedArena object to
201   // avoid ABA problem. Called with lock_ acquired.
DeferArenaFreeing()202   void DeferArenaFreeing() REQUIRES(lock_) {
203     CHECK(unused_arenas_ == nullptr);
204     defer_arena_freeing_ = true;
205   }
206 
207   // Clear defer_arena_freeing_ and delete all unused arenas.
208   void DeleteUnusedArenas() REQUIRES(!lock_);
209 
210  private:
211   void FreeRangeLocked(uint8_t* range_begin, size_t range_size) REQUIRES(lock_);
212   // Add a map (to be visited by userfaultfd) to the pool of at least min_size
213   // and return its address.
214   uint8_t* AddMap(size_t min_size) REQUIRES(lock_);
215   // Add a private anonymous map prior to zygote fork to the pool and return its
216   // address.
217   uint8_t* AddPreZygoteForkMap(size_t size) REQUIRES(lock_);
218 
219   class Chunk {
220    public:
Chunk(uint8_t * addr,size_t size)221     Chunk(uint8_t* addr, size_t size) : addr_(addr), size_(size) {}
222     uint8_t* addr_;
223     size_t size_;
224   };
225 
226   class LessByChunkAddr {
227    public:
operator()228     bool operator()(const Chunk* a, const Chunk* b) const {
229       return std::less<uint8_t*>{}(a->addr_, b->addr_);
230     }
231   };
232 
233   class LessByChunkSize {
234    public:
235     // Since two chunks could have the same size, use addr when that happens.
operator()236     bool operator()(const Chunk* a, const Chunk* b) const {
237       return a->size_ < b->size_ ||
238              (a->size_ == b->size_ && std::less<uint8_t*>{}(a->addr_, b->addr_));
239     }
240   };
241 
242   class TrackedArenaEquals {
243    public:
operator()244     bool operator()(const TrackedArena* a, const TrackedArena* b) const {
245       return std::equal_to<uint8_t*>{}(a->Begin(), b->Begin());
246     }
247   };
248 
249   class TrackedArenaHash {
250    public:
operator()251     size_t operator()(const TrackedArena* arena) const {
252       return std::hash<size_t>{}(DivideByPageSize(reinterpret_cast<uintptr_t>(arena->Begin())));
253     }
254   };
255   using AllocatedArenaSet =
256       HashSet<TrackedArena*, DefaultEmptyFn<TrackedArena*>, TrackedArenaHash, TrackedArenaEquals>;
257 
258   mutable ReaderWriterMutex lock_;
259   std::vector<MemMap> maps_ GUARDED_BY(lock_);
260   std::set<Chunk*, LessByChunkSize> best_fit_allocs_ GUARDED_BY(lock_);
261   std::set<Chunk*, LessByChunkAddr> free_chunks_ GUARDED_BY(lock_);
262   // Set of allocated arenas. It's required to be able to find the arena
263   // corresponding to a given address.
264   AllocatedArenaSet allocated_arenas_ GUARDED_BY(lock_);
265   // Number of bytes allocated so far.
266   size_t bytes_allocated_ GUARDED_BY(lock_);
267   // To hold arenas that are freed while GC is happening. These are kept until
268   // the end of GC to avoid ABA problem.
269   TrackedArena* unused_arenas_ GUARDED_BY(lock_);
270   const char* name_;
271   // Flag to indicate that some arenas have been freed. This flag is used as an
272   // optimization by GC to know if it needs to find if the arena being visited
273   // has been freed or not. The flag is cleared in the compaction pause and read
274   // when linear-alloc space is concurrently visited updated to update GC roots.
275   bool defer_arena_freeing_ GUARDED_BY(lock_);
276   const bool low_4gb_;
277   // Set to true in zygote process so that all linear-alloc allocations are in
278   // private-anonymous mappings and not on userfaultfd visited pages. At
279   // first zygote fork, it's set to false, after which all allocations are done
280   // in userfaultfd visited space.
281   bool pre_zygote_fork_ GUARDED_BY(lock_);
282 
283   DISALLOW_COPY_AND_ASSIGN(GcVisitedArenaPool);
284 };
285 
286 // Allocator for class-table and intern-table hash-sets. It enables updating the
287 // roots concurrently page-by-page.
288 template <class T, AllocatorTag kTag>
289 class GcRootArenaAllocator {
290   TrackingAllocator<T, kTag> tracking_alloc_;
291 
292  public:
293   using value_type = T;
294 
295   // Used internally by STL data structures. This copy constructor needs to be implicit. Don't wrap
296   // the line because that would break cpplint's detection of the implicit constructor.
297   template <class U>
GcRootArenaAllocator(const GcRootArenaAllocator<U,kTag> & alloc)298   GcRootArenaAllocator([[maybe_unused]] const GcRootArenaAllocator<U, kTag>& alloc) noexcept {}  // NOLINT [runtime/explicit]
299   // Used internally by STL data structures.
GcRootArenaAllocator()300   GcRootArenaAllocator() noexcept {}
301 
302   // Enables an allocator for objects of one type to allocate storage for objects of another type.
303   // Used internally by STL data structures.
304   template <class U>
305   struct rebind {
306     using other = GcRootArenaAllocator<U, kTag>;
307   };
308 
allocate(size_t n)309   T* allocate(size_t n) {
310     if (!gUseUserfaultfd) {
311       return tracking_alloc_.allocate(n);
312     }
313     size_t size = n * sizeof(T);
314     GcVisitedArenaPool* pool =
315         down_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool());
316     return reinterpret_cast<T*>(pool->AllocSingleObjArena(size));
317   }
318 
deallocate(T * p,size_t n)319   void deallocate(T* p, size_t n) {
320     if (!gUseUserfaultfd) {
321       tracking_alloc_.deallocate(p, n);
322       return;
323     }
324     GcVisitedArenaPool* pool =
325         down_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool());
326     pool->FreeSingleObjArena(reinterpret_cast<uint8_t*>(p));
327   }
328 };
329 
330 }  // namespace art
331 
332 #endif  // ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_
333