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 #include "base/gc_visited_arena_pool.h"
18
19 #include <sys/mman.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22
23 #include "base/arena_allocator-inl.h"
24 #include "base/memfd.h"
25 #include "base/utils.h"
26 #include "gc/collector/mark_compact-inl.h"
27
28 namespace art HIDDEN {
29
TrackedArena(uint8_t * start,size_t size,bool pre_zygote_fork,bool single_obj_arena)30 TrackedArena::TrackedArena(uint8_t* start, size_t size, bool pre_zygote_fork, bool single_obj_arena)
31 : Arena(),
32 first_obj_array_(nullptr),
33 pre_zygote_fork_(pre_zygote_fork),
34 waiting_for_deletion_(false) {
35 static_assert(ArenaAllocator::kArenaAlignment <= kMinPageSize,
36 "Arena should not need stronger alignment than kMinPageSize.");
37 memory_ = start;
38 size_ = size;
39 if (single_obj_arena) {
40 // We have only one object in this arena and it is expected to consume the
41 // entire arena.
42 bytes_allocated_ = size;
43 } else {
44 DCHECK_ALIGNED_PARAM(size, gPageSize);
45 DCHECK_ALIGNED_PARAM(start, gPageSize);
46 size_t arr_size = DivideByPageSize(size);
47 first_obj_array_.reset(new uint8_t*[arr_size]);
48 std::fill_n(first_obj_array_.get(), arr_size, nullptr);
49 }
50 }
51
ReleasePages(uint8_t * begin,size_t size,bool pre_zygote_fork)52 void TrackedArena::ReleasePages(uint8_t* begin, size_t size, bool pre_zygote_fork) {
53 DCHECK_ALIGNED_PARAM(begin, gPageSize);
54 // Userfaultfd GC uses MAP_SHARED mappings for linear-alloc and therefore
55 // MADV_DONTNEED will not free the pages from page cache. Therefore use
56 // MADV_REMOVE instead, which is meant for this purpose.
57 // Arenas allocated pre-zygote fork are private anonymous and hence must be
58 // released using MADV_DONTNEED.
59 if (!gUseUserfaultfd || pre_zygote_fork ||
60 (madvise(begin, size, MADV_REMOVE) == -1 && errno == EINVAL)) {
61 // MADV_REMOVE fails if invoked on anonymous mapping, which could happen
62 // if the arena is released before userfaultfd-GC starts using memfd. So
63 // use MADV_DONTNEED.
64 ZeroAndReleaseMemory(begin, size);
65 }
66 }
67
Release()68 void TrackedArena::Release() {
69 if (bytes_allocated_ > 0) {
70 ReleasePages(Begin(), Size(), pre_zygote_fork_);
71 if (first_obj_array_.get() != nullptr) {
72 std::fill_n(first_obj_array_.get(), DivideByPageSize(Size()), nullptr);
73 }
74 bytes_allocated_ = 0;
75 }
76 }
77
SetFirstObject(uint8_t * obj_begin,uint8_t * obj_end)78 void TrackedArena::SetFirstObject(uint8_t* obj_begin, uint8_t* obj_end) {
79 DCHECK(first_obj_array_.get() != nullptr);
80 DCHECK_LE(static_cast<void*>(Begin()), static_cast<void*>(obj_end));
81 DCHECK_LT(static_cast<void*>(obj_begin), static_cast<void*>(obj_end));
82 GcVisitedArenaPool* arena_pool =
83 static_cast<GcVisitedArenaPool*>(Runtime::Current()->GetLinearAllocArenaPool());
84 size_t idx = DivideByPageSize(static_cast<size_t>(obj_begin - Begin()));
85 size_t last_byte_idx = DivideByPageSize(static_cast<size_t>(obj_end - 1 - Begin()));
86 // Do the update below with arena-pool's lock in shared-mode to serialize with
87 // the compaction-pause wherein we acquire it exclusively. This is to ensure
88 // that last-byte read there doesn't change after reading it and before
89 // userfaultfd registration.
90 ReaderMutexLock rmu(Thread::Current(), arena_pool->GetLock());
91 // If the addr is at the beginning of a page, then we set it for that page too.
92 if (IsAlignedParam(obj_begin, gPageSize)) {
93 first_obj_array_[idx] = obj_begin;
94 }
95 while (idx < last_byte_idx) {
96 first_obj_array_[++idx] = obj_begin;
97 }
98 }
99
AddMap(size_t min_size)100 uint8_t* GcVisitedArenaPool::AddMap(size_t min_size) {
101 size_t size = std::max(min_size, kLinearAllocPoolSize);
102 #if defined(__LP64__)
103 // This is true only when we are running a 64-bit dex2oat to compile a 32-bit image.
104 if (low_4gb_) {
105 size = std::max(min_size, kLow4GBLinearAllocPoolSize);
106 }
107 #endif
108 size_t alignment = gc::Heap::BestPageTableAlignment(size);
109 DCHECK_GE(size, gc::Heap::GetPMDSize());
110 std::string err_msg;
111 maps_.emplace_back(MemMap::MapAnonymousAligned(
112 name_, size, PROT_READ | PROT_WRITE, low_4gb_, alignment, &err_msg));
113 MemMap& map = maps_.back();
114 if (!map.IsValid()) {
115 LOG(FATAL) << "Failed to allocate " << name_ << ": " << err_msg;
116 UNREACHABLE();
117 }
118
119 if (gUseUserfaultfd) {
120 // Create a shadow-map for the map being added for userfaultfd GC
121 gc::collector::MarkCompact* mark_compact =
122 Runtime::Current()->GetHeap()->MarkCompactCollector();
123 DCHECK_NE(mark_compact, nullptr);
124 mark_compact->AddLinearAllocSpaceData(map.Begin(), map.Size());
125 }
126 Chunk* chunk = new Chunk(map.Begin(), map.Size());
127 best_fit_allocs_.insert(chunk);
128 free_chunks_.insert(chunk);
129 return map.Begin();
130 }
131
GcVisitedArenaPool(bool low_4gb,bool is_zygote,const char * name)132 GcVisitedArenaPool::GcVisitedArenaPool(bool low_4gb, bool is_zygote, const char* name)
133 : lock_("gc-visited arena-pool", kGenericBottomLock),
134 bytes_allocated_(0),
135 unused_arenas_(nullptr),
136 name_(name),
137 defer_arena_freeing_(false),
138 low_4gb_(low_4gb),
139 pre_zygote_fork_(is_zygote) {}
140
~GcVisitedArenaPool()141 GcVisitedArenaPool::~GcVisitedArenaPool() {
142 for (Chunk* chunk : free_chunks_) {
143 delete chunk;
144 }
145 // Must not delete chunks from best_fit_allocs_ as they are shared with
146 // free_chunks_.
147 }
148
GetBytesAllocated() const149 size_t GcVisitedArenaPool::GetBytesAllocated() const {
150 ReaderMutexLock rmu(Thread::Current(), lock_);
151 return bytes_allocated_;
152 }
153
AddPreZygoteForkMap(size_t size)154 uint8_t* GcVisitedArenaPool::AddPreZygoteForkMap(size_t size) {
155 DCHECK(pre_zygote_fork_);
156 std::string pre_fork_name = "Pre-zygote-";
157 pre_fork_name += name_;
158 std::string err_msg;
159 maps_.emplace_back(MemMap::MapAnonymous(
160 pre_fork_name.c_str(), size, PROT_READ | PROT_WRITE, low_4gb_, &err_msg));
161 MemMap& map = maps_.back();
162 if (!map.IsValid()) {
163 LOG(FATAL) << "Failed to allocate " << pre_fork_name << ": " << err_msg;
164 UNREACHABLE();
165 }
166 return map.Begin();
167 }
168
AllocSingleObjArena(size_t size)169 uint8_t* GcVisitedArenaPool::AllocSingleObjArena(size_t size) {
170 WriterMutexLock wmu(Thread::Current(), lock_);
171 Arena* arena;
172 DCHECK(gUseUserfaultfd);
173 // To minimize private dirty, all class and intern table allocations are
174 // done outside LinearAlloc range so they are untouched during GC.
175 if (pre_zygote_fork_) {
176 uint8_t* begin = static_cast<uint8_t*>(malloc(size));
177 auto insert_result = allocated_arenas_.insert(
178 new TrackedArena(begin, size, /*pre_zygote_fork=*/true, /*single_obj_arena=*/true));
179 arena = *insert_result.first;
180 } else {
181 arena = AllocArena(size, /*need_first_obj_arr=*/true);
182 }
183 return arena->Begin();
184 }
185
FreeSingleObjArena(uint8_t * addr)186 void GcVisitedArenaPool::FreeSingleObjArena(uint8_t* addr) {
187 Thread* self = Thread::Current();
188 size_t size;
189 bool zygote_arena;
190 {
191 TrackedArena temp_arena(addr);
192 WriterMutexLock wmu(self, lock_);
193 auto iter = allocated_arenas_.find(&temp_arena);
194 DCHECK(iter != allocated_arenas_.end());
195 TrackedArena* arena = *iter;
196 size = arena->Size();
197 zygote_arena = arena->IsPreZygoteForkArena();
198 DCHECK_EQ(arena->Begin(), addr);
199 DCHECK(arena->IsSingleObjectArena());
200 allocated_arenas_.erase(iter);
201 if (defer_arena_freeing_) {
202 arena->SetupForDeferredDeletion(unused_arenas_);
203 unused_arenas_ = arena;
204 } else {
205 delete arena;
206 }
207 }
208 // Refer to the comment in FreeArenaChain() for why the pages are released
209 // after deleting the arena.
210 if (zygote_arena) {
211 free(addr);
212 } else {
213 TrackedArena::ReleasePages(addr, size, /*pre_zygote_fork=*/false);
214 WriterMutexLock wmu(self, lock_);
215 FreeRangeLocked(addr, size);
216 }
217 }
218
AllocArena(size_t size,bool single_obj_arena)219 Arena* GcVisitedArenaPool::AllocArena(size_t size, bool single_obj_arena) {
220 // Return only page aligned sizes so that madvise can be leveraged.
221 size = RoundUp(size, gPageSize);
222 if (pre_zygote_fork_) {
223 // The first fork out of zygote hasn't happened yet. Allocate arena in a
224 // private-anonymous mapping to retain clean pages across fork.
225 uint8_t* addr = AddPreZygoteForkMap(size);
226 auto insert_result = allocated_arenas_.insert(
227 new TrackedArena(addr, size, /*pre_zygote_fork=*/true, single_obj_arena));
228 DCHECK(insert_result.second);
229 return *insert_result.first;
230 }
231
232 Chunk temp_chunk(nullptr, size);
233 auto best_fit_iter = best_fit_allocs_.lower_bound(&temp_chunk);
234 if (UNLIKELY(best_fit_iter == best_fit_allocs_.end())) {
235 AddMap(size);
236 best_fit_iter = best_fit_allocs_.lower_bound(&temp_chunk);
237 CHECK(best_fit_iter != best_fit_allocs_.end());
238 }
239 auto free_chunks_iter = free_chunks_.find(*best_fit_iter);
240 DCHECK(free_chunks_iter != free_chunks_.end());
241 Chunk* chunk = *best_fit_iter;
242 DCHECK_EQ(chunk, *free_chunks_iter);
243 // if the best-fit chunk < 2x the requested size, then give the whole chunk.
244 if (chunk->size_ < 2 * size) {
245 DCHECK_GE(chunk->size_, size);
246 auto insert_result = allocated_arenas_.insert(new TrackedArena(chunk->addr_,
247 chunk->size_,
248 /*pre_zygote_fork=*/false,
249 single_obj_arena));
250 DCHECK(insert_result.second);
251 free_chunks_.erase(free_chunks_iter);
252 best_fit_allocs_.erase(best_fit_iter);
253 delete chunk;
254 return *insert_result.first;
255 } else {
256 auto insert_result = allocated_arenas_.insert(new TrackedArena(chunk->addr_,
257 size,
258 /*pre_zygote_fork=*/false,
259 single_obj_arena));
260 DCHECK(insert_result.second);
261 // Compute next iterators for faster insert later.
262 auto next_best_fit_iter = best_fit_iter;
263 next_best_fit_iter++;
264 auto next_free_chunks_iter = free_chunks_iter;
265 next_free_chunks_iter++;
266 auto best_fit_nh = best_fit_allocs_.extract(best_fit_iter);
267 auto free_chunks_nh = free_chunks_.extract(free_chunks_iter);
268 best_fit_nh.value()->addr_ += size;
269 best_fit_nh.value()->size_ -= size;
270 DCHECK_EQ(free_chunks_nh.value()->addr_, chunk->addr_);
271 best_fit_allocs_.insert(next_best_fit_iter, std::move(best_fit_nh));
272 free_chunks_.insert(next_free_chunks_iter, std::move(free_chunks_nh));
273 return *insert_result.first;
274 }
275 }
276
FreeRangeLocked(uint8_t * range_begin,size_t range_size)277 void GcVisitedArenaPool::FreeRangeLocked(uint8_t* range_begin, size_t range_size) {
278 Chunk temp_chunk(range_begin, range_size);
279 bool merge_with_next = false;
280 bool merge_with_prev = false;
281 auto next_iter = free_chunks_.lower_bound(&temp_chunk);
282 auto iter_for_extract = free_chunks_.end();
283 // Can we merge with the previous chunk?
284 if (next_iter != free_chunks_.begin()) {
285 auto prev_iter = next_iter;
286 prev_iter--;
287 merge_with_prev = (*prev_iter)->addr_ + (*prev_iter)->size_ == range_begin;
288 if (merge_with_prev) {
289 range_begin = (*prev_iter)->addr_;
290 range_size += (*prev_iter)->size_;
291 // Hold on to the iterator for faster extract later
292 iter_for_extract = prev_iter;
293 }
294 }
295 // Can we merge with the next chunk?
296 if (next_iter != free_chunks_.end()) {
297 merge_with_next = range_begin + range_size == (*next_iter)->addr_;
298 if (merge_with_next) {
299 range_size += (*next_iter)->size_;
300 if (merge_with_prev) {
301 auto iter = next_iter;
302 next_iter++;
303 // Keep only one of the two chunks to be expanded.
304 Chunk* chunk = *iter;
305 size_t erase_res = best_fit_allocs_.erase(chunk);
306 DCHECK_EQ(erase_res, 1u);
307 free_chunks_.erase(iter);
308 delete chunk;
309 } else {
310 iter_for_extract = next_iter;
311 next_iter++;
312 }
313 }
314 }
315
316 // Extract-insert avoids 2/4 destroys and 2/2 creations
317 // as compared to erase-insert, so use that when merging.
318 if (merge_with_prev || merge_with_next) {
319 auto free_chunks_nh = free_chunks_.extract(iter_for_extract);
320 auto best_fit_allocs_nh = best_fit_allocs_.extract(*iter_for_extract);
321
322 free_chunks_nh.value()->addr_ = range_begin;
323 DCHECK_EQ(best_fit_allocs_nh.value()->addr_, range_begin);
324 free_chunks_nh.value()->size_ = range_size;
325 DCHECK_EQ(best_fit_allocs_nh.value()->size_, range_size);
326
327 free_chunks_.insert(next_iter, std::move(free_chunks_nh));
328 // Since the chunk's size has expanded, the hint won't be useful
329 // for best-fit set.
330 best_fit_allocs_.insert(std::move(best_fit_allocs_nh));
331 } else {
332 DCHECK(iter_for_extract == free_chunks_.end());
333 Chunk* chunk = new Chunk(range_begin, range_size);
334 free_chunks_.insert(next_iter, chunk);
335 best_fit_allocs_.insert(chunk);
336 }
337 }
338
FreeArenaChain(Arena * first)339 void GcVisitedArenaPool::FreeArenaChain(Arena* first) {
340 if (kRunningOnMemoryTool) {
341 for (Arena* arena = first; arena != nullptr; arena = arena->Next()) {
342 MEMORY_TOOL_MAKE_UNDEFINED(arena->Begin(), arena->GetBytesAllocated());
343 }
344 }
345
346 // TODO: Handle the case when arena_allocator::kArenaAllocatorPreciseTracking
347 // is true. See MemMapArenaPool::FreeArenaChain() for example.
348 CHECK(!arena_allocator::kArenaAllocatorPreciseTracking);
349 Thread* self = Thread::Current();
350 // vector of arena ranges to be freed and whether they are pre-zygote-fork.
351 std::vector<std::tuple<uint8_t*, size_t, bool>> free_ranges;
352
353 {
354 WriterMutexLock wmu(self, lock_);
355 while (first != nullptr) {
356 TrackedArena* temp = down_cast<TrackedArena*>(first);
357 DCHECK(!temp->IsSingleObjectArena());
358 first = first->Next();
359 free_ranges.emplace_back(temp->Begin(), temp->Size(), temp->IsPreZygoteForkArena());
360 // In other implementations of ArenaPool this is calculated when asked for,
361 // thanks to the list of free arenas that is kept around. But in this case,
362 // we release the freed arena back to the pool and therefore need to
363 // calculate here.
364 bytes_allocated_ += temp->GetBytesAllocated();
365 auto iter = allocated_arenas_.find(temp);
366 DCHECK(iter != allocated_arenas_.end());
367 allocated_arenas_.erase(iter);
368 if (defer_arena_freeing_) {
369 temp->SetupForDeferredDeletion(unused_arenas_);
370 unused_arenas_ = temp;
371 } else {
372 delete temp;
373 }
374 }
375 }
376
377 // madvise of arenas must be done after the above loop which serializes with
378 // MarkCompact::ProcessLinearAlloc() so that if it finds an arena to be not
379 // 'waiting-for-deletion' then it finishes the arena's processing before
380 // clearing here. Otherwise, we could have a situation wherein arena-pool
381 // assumes the memory range of the arena(s) to be zero'ed (by madvise),
382 // whereas GC maps stale arena pages.
383 for (auto& iter : free_ranges) {
384 // No need to madvise pre-zygote-fork arenas as they will munmapped below.
385 if (!std::get<2>(iter)) {
386 TrackedArena::ReleasePages(std::get<0>(iter), std::get<1>(iter), /*pre_zygote_fork=*/false);
387 }
388 }
389
390 WriterMutexLock wmu(self, lock_);
391 for (auto& iter : free_ranges) {
392 if (UNLIKELY(std::get<2>(iter))) {
393 bool found = false;
394 for (auto map_iter = maps_.begin(); map_iter != maps_.end(); map_iter++) {
395 if (map_iter->Begin() == std::get<0>(iter)) {
396 // erase will destruct the MemMap and thereby munmap. But this happens
397 // very rarely so it's ok to do it with lock acquired.
398 maps_.erase(map_iter);
399 found = true;
400 break;
401 }
402 }
403 CHECK(found);
404 } else {
405 FreeRangeLocked(std::get<0>(iter), std::get<1>(iter));
406 }
407 }
408 }
409
DeleteUnusedArenas()410 void GcVisitedArenaPool::DeleteUnusedArenas() {
411 TrackedArena* arena;
412 {
413 WriterMutexLock wmu(Thread::Current(), lock_);
414 defer_arena_freeing_ = false;
415 arena = unused_arenas_;
416 unused_arenas_ = nullptr;
417 }
418 while (arena != nullptr) {
419 TrackedArena* temp = down_cast<TrackedArena*>(arena->Next());
420 delete arena;
421 arena = temp;
422 }
423 }
424
425 } // namespace art
426