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