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