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 // Madvise the pages in the given range. 'begin' is expected to be page 116 // aligned. 117 // TODO: Remove this once we remove the shmem (minor-fault) code in 118 // userfaultfd GC and directly use ZeroAndReleaseMemory(). 119 static void ReleasePages(uint8_t* begin, size_t size, bool pre_zygote_fork); 120 void Release() override; IsPreZygoteForkArena()121 bool IsPreZygoteForkArena() const { return pre_zygote_fork_; } IsSingleObjectArena()122 bool IsSingleObjectArena() const { return first_obj_array_.get() == nullptr; } 123 124 private: 125 // first_obj_array_[i] is the object that overlaps with the ith page's 126 // beginning, i.e. first_obj_array_[i] <= ith page_begin. 127 std::unique_ptr<uint8_t*[]> first_obj_array_; 128 const bool pre_zygote_fork_; 129 bool waiting_for_deletion_; 130 }; 131 132 // An arena-pool wherein allocations can be tracked so that the GC can visit all 133 // the GC roots. All the arenas are allocated in one sufficiently large memory 134 // range to avoid multiple calls to mremapped/mprotected syscalls. 135 class GcVisitedArenaPool final : public ArenaPool { 136 public: 137 #if defined(__LP64__) 138 // Use a size in multiples of 1GB as that can utilize the optimized mremap 139 // page-table move. 140 static constexpr size_t kLinearAllocPoolSize = 1 * GB; 141 static constexpr size_t kLow4GBLinearAllocPoolSize = 32 * MB; 142 #else 143 static constexpr size_t kLinearAllocPoolSize = 32 * MB; 144 #endif 145 146 explicit GcVisitedArenaPool(bool low_4gb = false, 147 bool is_zygote = false, 148 const char* name = "LinearAlloc"); 149 virtual ~GcVisitedArenaPool(); 150 151 Arena* AllocArena(size_t size, bool need_first_obj_arr) REQUIRES(lock_); 152 // Use by arena allocator. AllocArena(size_t size)153 Arena* AllocArena(size_t size) override REQUIRES(!lock_) { 154 WriterMutexLock wmu(Thread::Current(), lock_); 155 return AllocArena(size, /*need_first_obj_arr=*/false); 156 } 157 void FreeArenaChain(Arena* first) override REQUIRES(!lock_); 158 size_t GetBytesAllocated() const override REQUIRES(!lock_); ReclaimMemory()159 void ReclaimMemory() override {} LockReclaimMemory()160 void LockReclaimMemory() override {} TrimMaps()161 void TrimMaps() override {} 162 163 EXPORT uint8_t* AllocSingleObjArena(size_t size) REQUIRES(!lock_); 164 EXPORT void FreeSingleObjArena(uint8_t* addr) REQUIRES(!lock_); 165 Contains(void * ptr)166 bool Contains(void* ptr) REQUIRES(!lock_) { 167 ReaderMutexLock rmu(Thread::Current(), lock_); 168 for (auto& map : maps_) { 169 if (map.HasAddress(ptr)) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 template <typename PageVisitor> VisitRoots(PageVisitor & visitor)177 void VisitRoots(PageVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_, lock_) { 178 for (auto& arena : allocated_arenas_) { 179 arena->VisitRoots(visitor); 180 } 181 } 182 183 template <typename Callback> ForEachAllocatedArena(Callback cb)184 void ForEachAllocatedArena(Callback cb) REQUIRES_SHARED(Locks::mutator_lock_, lock_) { 185 // We should not have any unused arenas when calling this function. 186 CHECK(unused_arenas_ == nullptr); 187 for (auto& arena : allocated_arenas_) { 188 cb(*arena); 189 } 190 } 191 192 // Called in Heap::PreZygoteFork(). All allocations after this are done in 193 // arena-pool which is visited by userfaultfd. SetupPostZygoteMode()194 void SetupPostZygoteMode() REQUIRES(!lock_) { 195 WriterMutexLock wmu(Thread::Current(), lock_); 196 DCHECK(pre_zygote_fork_); 197 pre_zygote_fork_ = false; 198 } 199 200 // For userfaultfd GC to be able to acquire the lock to avoid concurrent 201 // release of arenas when it is visiting them. GetLock()202 ReaderWriterMutex& GetLock() const RETURN_CAPABILITY(lock_) { return lock_; } 203 204 // Called in the compaction pause to indicate that all arenas that will be 205 // freed until compaction is done shouldn't delete the TrackedArena object to 206 // avoid ABA problem. Called with lock_ acquired. DeferArenaFreeing()207 void DeferArenaFreeing() REQUIRES(lock_) { 208 CHECK(unused_arenas_ == nullptr); 209 defer_arena_freeing_ = true; 210 } 211 212 // Clear defer_arena_freeing_ and delete all unused arenas. 213 void DeleteUnusedArenas() REQUIRES(!lock_); 214 215 private: 216 void FreeRangeLocked(uint8_t* range_begin, size_t range_size) REQUIRES(lock_); 217 // Add a map (to be visited by userfaultfd) to the pool of at least min_size 218 // and return its address. 219 uint8_t* AddMap(size_t min_size) REQUIRES(lock_); 220 // Add a private anonymous map prior to zygote fork to the pool and return its 221 // address. 222 uint8_t* AddPreZygoteForkMap(size_t size) REQUIRES(lock_); 223 224 class Chunk { 225 public: Chunk(uint8_t * addr,size_t size)226 Chunk(uint8_t* addr, size_t size) : addr_(addr), size_(size) {} 227 uint8_t* addr_; 228 size_t size_; 229 }; 230 231 class LessByChunkAddr { 232 public: operator()233 bool operator()(const Chunk* a, const Chunk* b) const { 234 return std::less<uint8_t*>{}(a->addr_, b->addr_); 235 } 236 }; 237 238 class LessByChunkSize { 239 public: 240 // Since two chunks could have the same size, use addr when that happens. operator()241 bool operator()(const Chunk* a, const Chunk* b) const { 242 return a->size_ < b->size_ || 243 (a->size_ == b->size_ && std::less<uint8_t*>{}(a->addr_, b->addr_)); 244 } 245 }; 246 247 class TrackedArenaEquals { 248 public: operator()249 bool operator()(const TrackedArena* a, const TrackedArena* b) const { 250 return std::equal_to<uint8_t*>{}(a->Begin(), b->Begin()); 251 } 252 }; 253 254 class TrackedArenaHash { 255 public: operator()256 size_t operator()(const TrackedArena* arena) const { 257 return std::hash<size_t>{}(DivideByPageSize(reinterpret_cast<uintptr_t>(arena->Begin()))); 258 } 259 }; 260 using AllocatedArenaSet = 261 HashSet<TrackedArena*, DefaultEmptyFn<TrackedArena*>, TrackedArenaHash, TrackedArenaEquals>; 262 263 mutable ReaderWriterMutex lock_; 264 std::vector<MemMap> maps_ GUARDED_BY(lock_); 265 std::set<Chunk*, LessByChunkSize> best_fit_allocs_ GUARDED_BY(lock_); 266 std::set<Chunk*, LessByChunkAddr> free_chunks_ GUARDED_BY(lock_); 267 // Set of allocated arenas. It's required to be able to find the arena 268 // corresponding to a given address. 269 AllocatedArenaSet allocated_arenas_ GUARDED_BY(lock_); 270 // Number of bytes allocated so far. 271 size_t bytes_allocated_ GUARDED_BY(lock_); 272 // To hold arenas that are freed while GC is happening. These are kept until 273 // the end of GC to avoid ABA problem. 274 TrackedArena* unused_arenas_ GUARDED_BY(lock_); 275 const char* name_; 276 // Flag to indicate that some arenas have been freed. This flag is used as an 277 // optimization by GC to know if it needs to find if the arena being visited 278 // has been freed or not. The flag is cleared in the compaction pause and read 279 // when linear-alloc space is concurrently visited updated to update GC roots. 280 bool defer_arena_freeing_ GUARDED_BY(lock_); 281 const bool low_4gb_; 282 // Set to true in zygote process so that all linear-alloc allocations are in 283 // private-anonymous mappings and not on userfaultfd visited pages. At 284 // first zygote fork, it's set to false, after which all allocations are done 285 // in userfaultfd visited space. 286 bool pre_zygote_fork_ GUARDED_BY(lock_); 287 288 DISALLOW_COPY_AND_ASSIGN(GcVisitedArenaPool); 289 }; 290 291 // Allocator for class-table and intern-table hash-sets. It enables updating the 292 // roots concurrently page-by-page. 293 template <class T, AllocatorTag kTag> 294 class GcRootArenaAllocator { 295 TrackingAllocator<T, kTag> tracking_alloc_; 296 297 public: 298 using value_type = T; 299 300 // Used internally by STL data structures. This copy constructor needs to be implicit. Don't wrap 301 // the line because that would break cpplint's detection of the implicit constructor. 302 template <class U> GcRootArenaAllocator(const GcRootArenaAllocator<U,kTag> & alloc)303 GcRootArenaAllocator([[maybe_unused]] const GcRootArenaAllocator<U, kTag>& alloc) noexcept {} // NOLINT [runtime/explicit] 304 // Used internally by STL data structures. GcRootArenaAllocator()305 GcRootArenaAllocator() noexcept {} 306 307 // Enables an allocator for objects of one type to allocate storage for objects of another type. 308 // Used internally by STL data structures. 309 template <class U> 310 struct rebind { 311 using other = GcRootArenaAllocator<U, kTag>; 312 }; 313 allocate(size_t n)314 T* allocate(size_t n) { 315 if (!gUseUserfaultfd) { 316 return tracking_alloc_.allocate(n); 317 } 318 size_t size = n * sizeof(T); 319 GcVisitedArenaPool* pool = 320 down_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool()); 321 return reinterpret_cast<T*>(pool->AllocSingleObjArena(size)); 322 } 323 deallocate(T * p,size_t n)324 void deallocate(T* p, size_t n) { 325 if (!gUseUserfaultfd) { 326 tracking_alloc_.deallocate(p, n); 327 return; 328 } 329 GcVisitedArenaPool* pool = 330 down_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool()); 331 pool->FreeSingleObjArena(reinterpret_cast<uint8_t*>(p)); 332 } 333 }; 334 335 } // namespace art 336 337 #endif // ART_RUNTIME_BASE_GC_VISITED_ARENA_POOL_H_ 338