xref: /aosp_15_r20/art/dex2oat/utils/swap_space.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1*795d594fSAndroid Build Coastguard Worker /*
2*795d594fSAndroid Build Coastguard Worker  * Copyright (C) 2014 The Android Open Source Project
3*795d594fSAndroid Build Coastguard Worker  *
4*795d594fSAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*795d594fSAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*795d594fSAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*795d594fSAndroid Build Coastguard Worker  *
8*795d594fSAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*795d594fSAndroid Build Coastguard Worker  *
10*795d594fSAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*795d594fSAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*795d594fSAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*795d594fSAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*795d594fSAndroid Build Coastguard Worker  * limitations under the License.
15*795d594fSAndroid Build Coastguard Worker  */
16*795d594fSAndroid Build Coastguard Worker 
17*795d594fSAndroid Build Coastguard Worker #include "swap_space.h"
18*795d594fSAndroid Build Coastguard Worker 
19*795d594fSAndroid Build Coastguard Worker #include <sys/mman.h>
20*795d594fSAndroid Build Coastguard Worker 
21*795d594fSAndroid Build Coastguard Worker #include <algorithm>
22*795d594fSAndroid Build Coastguard Worker #include <numeric>
23*795d594fSAndroid Build Coastguard Worker 
24*795d594fSAndroid Build Coastguard Worker #include "base/bit_utils.h"
25*795d594fSAndroid Build Coastguard Worker #include "base/mem_map.h"
26*795d594fSAndroid Build Coastguard Worker #include "base/macros.h"
27*795d594fSAndroid Build Coastguard Worker #include "base/mutex.h"
28*795d594fSAndroid Build Coastguard Worker #include "thread-current-inl.h"
29*795d594fSAndroid Build Coastguard Worker 
30*795d594fSAndroid Build Coastguard Worker namespace art {
31*795d594fSAndroid Build Coastguard Worker 
32*795d594fSAndroid Build Coastguard Worker // The chunk size by which the swap file is increased and mapped.
33*795d594fSAndroid Build Coastguard Worker static constexpr size_t kMinimumMapSize = 16 * MB;
34*795d594fSAndroid Build Coastguard Worker 
35*795d594fSAndroid Build Coastguard Worker static constexpr bool kCheckFreeMaps = false;
36*795d594fSAndroid Build Coastguard Worker 
37*795d594fSAndroid Build Coastguard Worker template <typename FreeBySizeSet>
DumpFreeMap(const FreeBySizeSet & free_by_size)38*795d594fSAndroid Build Coastguard Worker static void DumpFreeMap(const FreeBySizeSet& free_by_size) {
39*795d594fSAndroid Build Coastguard Worker   size_t last_size = static_cast<size_t>(-1);
40*795d594fSAndroid Build Coastguard Worker   for (const auto& entry : free_by_size) {
41*795d594fSAndroid Build Coastguard Worker     if (last_size != entry.size) {
42*795d594fSAndroid Build Coastguard Worker       last_size = entry.size;
43*795d594fSAndroid Build Coastguard Worker       LOG(INFO) << "Size " << last_size;
44*795d594fSAndroid Build Coastguard Worker     }
45*795d594fSAndroid Build Coastguard Worker     LOG(INFO) << "  0x" << std::hex << entry.free_by_start_entry->Start()
46*795d594fSAndroid Build Coastguard Worker         << " size=" << std::dec << entry.free_by_start_entry->size;
47*795d594fSAndroid Build Coastguard Worker   }
48*795d594fSAndroid Build Coastguard Worker }
49*795d594fSAndroid Build Coastguard Worker 
RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos)50*795d594fSAndroid Build Coastguard Worker void SwapSpace::RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos) {
51*795d594fSAndroid Build Coastguard Worker   auto free_by_start_pos = free_by_size_pos->free_by_start_entry;
52*795d594fSAndroid Build Coastguard Worker   free_by_size_.erase(free_by_size_pos);
53*795d594fSAndroid Build Coastguard Worker   free_by_start_.erase(free_by_start_pos);
54*795d594fSAndroid Build Coastguard Worker }
55*795d594fSAndroid Build Coastguard Worker 
InsertChunk(const SpaceChunk & chunk)56*795d594fSAndroid Build Coastguard Worker inline void SwapSpace::InsertChunk(const SpaceChunk& chunk) {
57*795d594fSAndroid Build Coastguard Worker   DCHECK_NE(chunk.size, 0u);
58*795d594fSAndroid Build Coastguard Worker   auto insert_result = free_by_start_.insert(chunk);
59*795d594fSAndroid Build Coastguard Worker   DCHECK(insert_result.second);
60*795d594fSAndroid Build Coastguard Worker   free_by_size_.emplace(chunk.size, insert_result.first);
61*795d594fSAndroid Build Coastguard Worker }
62*795d594fSAndroid Build Coastguard Worker 
SwapSpace(int fd,size_t initial_size)63*795d594fSAndroid Build Coastguard Worker SwapSpace::SwapSpace(int fd, size_t initial_size)
64*795d594fSAndroid Build Coastguard Worker     : fd_(fd),
65*795d594fSAndroid Build Coastguard Worker       size_(0),
66*795d594fSAndroid Build Coastguard Worker       lock_("SwapSpace lock", static_cast<LockLevel>(LockLevel::kDefaultMutexLevel - 1)) {
67*795d594fSAndroid Build Coastguard Worker   // Assume that the file is unlinked.
68*795d594fSAndroid Build Coastguard Worker 
69*795d594fSAndroid Build Coastguard Worker   InsertChunk(NewFileChunk(initial_size));
70*795d594fSAndroid Build Coastguard Worker }
71*795d594fSAndroid Build Coastguard Worker 
~SwapSpace()72*795d594fSAndroid Build Coastguard Worker SwapSpace::~SwapSpace() {
73*795d594fSAndroid Build Coastguard Worker   // Unmap all mmapped chunks. Nothing should be allocated anymore at
74*795d594fSAndroid Build Coastguard Worker   // this point, so there should be only full size chunks in free_by_start_.
75*795d594fSAndroid Build Coastguard Worker   for (const SpaceChunk& chunk : free_by_start_) {
76*795d594fSAndroid Build Coastguard Worker     if (munmap(chunk.ptr, chunk.size) != 0) {
77*795d594fSAndroid Build Coastguard Worker       PLOG(ERROR) << "Failed to unmap swap space chunk at "
78*795d594fSAndroid Build Coastguard Worker           << static_cast<const void*>(chunk.ptr) << " size=" << chunk.size;
79*795d594fSAndroid Build Coastguard Worker     }
80*795d594fSAndroid Build Coastguard Worker   }
81*795d594fSAndroid Build Coastguard Worker   // All arenas are backed by the same file. Just close the descriptor.
82*795d594fSAndroid Build Coastguard Worker   close(fd_);
83*795d594fSAndroid Build Coastguard Worker }
84*795d594fSAndroid Build Coastguard Worker 
85*795d594fSAndroid Build Coastguard Worker template <typename FreeByStartSet, typename FreeBySizeSet>
CollectFree(const FreeByStartSet & free_by_start,const FreeBySizeSet & free_by_size)86*795d594fSAndroid Build Coastguard Worker static size_t CollectFree(const FreeByStartSet& free_by_start, const FreeBySizeSet& free_by_size) {
87*795d594fSAndroid Build Coastguard Worker   if (free_by_start.size() != free_by_size.size()) {
88*795d594fSAndroid Build Coastguard Worker     LOG(FATAL) << "Size: " << free_by_start.size() << " vs " << free_by_size.size();
89*795d594fSAndroid Build Coastguard Worker   }
90*795d594fSAndroid Build Coastguard Worker 
91*795d594fSAndroid Build Coastguard Worker   // Calculate over free_by_size.
92*795d594fSAndroid Build Coastguard Worker   size_t sum1 = 0;
93*795d594fSAndroid Build Coastguard Worker   for (const auto& entry : free_by_size) {
94*795d594fSAndroid Build Coastguard Worker     sum1 += entry.free_by_start_entry->size;
95*795d594fSAndroid Build Coastguard Worker   }
96*795d594fSAndroid Build Coastguard Worker 
97*795d594fSAndroid Build Coastguard Worker   // Calculate over free_by_start.
98*795d594fSAndroid Build Coastguard Worker   size_t sum2 = 0;
99*795d594fSAndroid Build Coastguard Worker   for (const auto& entry : free_by_start) {
100*795d594fSAndroid Build Coastguard Worker     sum2 += entry.size;
101*795d594fSAndroid Build Coastguard Worker   }
102*795d594fSAndroid Build Coastguard Worker 
103*795d594fSAndroid Build Coastguard Worker   if (sum1 != sum2) {
104*795d594fSAndroid Build Coastguard Worker     LOG(FATAL) << "Sum: " << sum1 << " vs " << sum2;
105*795d594fSAndroid Build Coastguard Worker   }
106*795d594fSAndroid Build Coastguard Worker   return sum1;
107*795d594fSAndroid Build Coastguard Worker }
108*795d594fSAndroid Build Coastguard Worker 
Alloc(size_t size)109*795d594fSAndroid Build Coastguard Worker void* SwapSpace::Alloc(size_t size) {
110*795d594fSAndroid Build Coastguard Worker   MutexLock lock(Thread::Current(), lock_);
111*795d594fSAndroid Build Coastguard Worker   size = RoundUp(size, 8U);
112*795d594fSAndroid Build Coastguard Worker 
113*795d594fSAndroid Build Coastguard Worker   // Check the free list for something that fits.
114*795d594fSAndroid Build Coastguard Worker   // TODO: Smarter implementation. Global biggest chunk, ...
115*795d594fSAndroid Build Coastguard Worker   auto it = free_by_start_.empty()
116*795d594fSAndroid Build Coastguard Worker       ? free_by_size_.end()
117*795d594fSAndroid Build Coastguard Worker       : free_by_size_.lower_bound(FreeBySizeEntry { size, free_by_start_.begin() });
118*795d594fSAndroid Build Coastguard Worker   if (it != free_by_size_.end()) {
119*795d594fSAndroid Build Coastguard Worker     SpaceChunk old_chunk = *it->free_by_start_entry;
120*795d594fSAndroid Build Coastguard Worker     if (old_chunk.size == size) {
121*795d594fSAndroid Build Coastguard Worker       RemoveChunk(it);
122*795d594fSAndroid Build Coastguard Worker     } else {
123*795d594fSAndroid Build Coastguard Worker       // Avoid deallocating and allocating the std::set<> nodes.
124*795d594fSAndroid Build Coastguard Worker       // This would be much simpler if we could use replace() from Boost.Bimap.
125*795d594fSAndroid Build Coastguard Worker 
126*795d594fSAndroid Build Coastguard Worker       // The free_by_start_ map contains disjoint intervals ordered by the `ptr`.
127*795d594fSAndroid Build Coastguard Worker       // Shrinking the interval does not affect the ordering.
128*795d594fSAndroid Build Coastguard Worker       it->free_by_start_entry->ptr += size;
129*795d594fSAndroid Build Coastguard Worker       it->free_by_start_entry->size -= size;
130*795d594fSAndroid Build Coastguard Worker 
131*795d594fSAndroid Build Coastguard Worker       auto node = free_by_size_.extract(it);
132*795d594fSAndroid Build Coastguard Worker       node.value().size -= size;
133*795d594fSAndroid Build Coastguard Worker       free_by_size_.insert(std::move(node));
134*795d594fSAndroid Build Coastguard Worker     }
135*795d594fSAndroid Build Coastguard Worker     return old_chunk.ptr;
136*795d594fSAndroid Build Coastguard Worker   } else {
137*795d594fSAndroid Build Coastguard Worker     // Not a big enough free chunk, need to increase file size.
138*795d594fSAndroid Build Coastguard Worker     SpaceChunk new_chunk = NewFileChunk(size);
139*795d594fSAndroid Build Coastguard Worker     if (new_chunk.size != size) {
140*795d594fSAndroid Build Coastguard Worker       // Insert the remainder.
141*795d594fSAndroid Build Coastguard Worker       SpaceChunk remainder = { new_chunk.ptr + size, new_chunk.size - size };
142*795d594fSAndroid Build Coastguard Worker       InsertChunk(remainder);
143*795d594fSAndroid Build Coastguard Worker     }
144*795d594fSAndroid Build Coastguard Worker     return new_chunk.ptr;
145*795d594fSAndroid Build Coastguard Worker   }
146*795d594fSAndroid Build Coastguard Worker }
147*795d594fSAndroid Build Coastguard Worker 
NewFileChunk(size_t min_size)148*795d594fSAndroid Build Coastguard Worker SwapSpace::SpaceChunk SwapSpace::NewFileChunk(size_t min_size) {
149*795d594fSAndroid Build Coastguard Worker #if !defined(__APPLE__)
150*795d594fSAndroid Build Coastguard Worker   const size_t page_size = MemMap::GetPageSize();
151*795d594fSAndroid Build Coastguard Worker   size_t next_part = std::max(RoundUp(min_size, page_size), RoundUp(kMinimumMapSize, page_size));
152*795d594fSAndroid Build Coastguard Worker   int result = TEMP_FAILURE_RETRY(ftruncate64(fd_, size_ + next_part));
153*795d594fSAndroid Build Coastguard Worker   if (result != 0) {
154*795d594fSAndroid Build Coastguard Worker     PLOG(FATAL) << "Unable to increase swap file.";
155*795d594fSAndroid Build Coastguard Worker   }
156*795d594fSAndroid Build Coastguard Worker   uint8_t* ptr = reinterpret_cast<uint8_t*>(
157*795d594fSAndroid Build Coastguard Worker       mmap(nullptr, next_part, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, size_));
158*795d594fSAndroid Build Coastguard Worker   if (ptr == MAP_FAILED) {
159*795d594fSAndroid Build Coastguard Worker     LOG(ERROR) << "Unable to mmap new swap file chunk.";
160*795d594fSAndroid Build Coastguard Worker     LOG(ERROR) << "Current size: " << size_ << " requested: " << next_part << "/" << min_size;
161*795d594fSAndroid Build Coastguard Worker     LOG(ERROR) << "Free list:";
162*795d594fSAndroid Build Coastguard Worker     DumpFreeMap(free_by_size_);
163*795d594fSAndroid Build Coastguard Worker     LOG(ERROR) << "In free list: " << CollectFree(free_by_start_, free_by_size_);
164*795d594fSAndroid Build Coastguard Worker     PLOG(FATAL) << "Unable to mmap new swap file chunk.";
165*795d594fSAndroid Build Coastguard Worker   }
166*795d594fSAndroid Build Coastguard Worker   size_ += next_part;
167*795d594fSAndroid Build Coastguard Worker   SpaceChunk new_chunk = {ptr, next_part};
168*795d594fSAndroid Build Coastguard Worker   return new_chunk;
169*795d594fSAndroid Build Coastguard Worker #else
170*795d594fSAndroid Build Coastguard Worker   UNUSED(min_size, kMinimumMapSize);
171*795d594fSAndroid Build Coastguard Worker   LOG(FATAL) << "No swap file support on the Mac.";
172*795d594fSAndroid Build Coastguard Worker   UNREACHABLE();
173*795d594fSAndroid Build Coastguard Worker #endif
174*795d594fSAndroid Build Coastguard Worker }
175*795d594fSAndroid Build Coastguard Worker 
176*795d594fSAndroid Build Coastguard Worker // TODO: Full coalescing.
Free(void * ptr,size_t size)177*795d594fSAndroid Build Coastguard Worker void SwapSpace::Free(void* ptr, size_t size) {
178*795d594fSAndroid Build Coastguard Worker   MutexLock lock(Thread::Current(), lock_);
179*795d594fSAndroid Build Coastguard Worker   size = RoundUp(size, 8U);
180*795d594fSAndroid Build Coastguard Worker 
181*795d594fSAndroid Build Coastguard Worker   size_t free_before = 0;
182*795d594fSAndroid Build Coastguard Worker   if (kCheckFreeMaps) {
183*795d594fSAndroid Build Coastguard Worker     free_before = CollectFree(free_by_start_, free_by_size_);
184*795d594fSAndroid Build Coastguard Worker   }
185*795d594fSAndroid Build Coastguard Worker 
186*795d594fSAndroid Build Coastguard Worker   SpaceChunk chunk = { reinterpret_cast<uint8_t*>(ptr), size };
187*795d594fSAndroid Build Coastguard Worker   auto it = free_by_start_.lower_bound(chunk);
188*795d594fSAndroid Build Coastguard Worker   if (it != free_by_start_.begin()) {
189*795d594fSAndroid Build Coastguard Worker     auto prev = it;
190*795d594fSAndroid Build Coastguard Worker     --prev;
191*795d594fSAndroid Build Coastguard Worker     CHECK_LE(prev->End(), chunk.Start());
192*795d594fSAndroid Build Coastguard Worker     if (prev->End() == chunk.Start()) {
193*795d594fSAndroid Build Coastguard Worker       // Merge *prev with this chunk.
194*795d594fSAndroid Build Coastguard Worker       chunk.size += prev->size;
195*795d594fSAndroid Build Coastguard Worker       chunk.ptr -= prev->size;
196*795d594fSAndroid Build Coastguard Worker       auto erase_pos = free_by_size_.find(FreeBySizeEntry { prev->size, prev });
197*795d594fSAndroid Build Coastguard Worker       DCHECK(erase_pos != free_by_size_.end());
198*795d594fSAndroid Build Coastguard Worker       RemoveChunk(erase_pos);
199*795d594fSAndroid Build Coastguard Worker       // "prev" is invalidated but "it" remains valid.
200*795d594fSAndroid Build Coastguard Worker     }
201*795d594fSAndroid Build Coastguard Worker   }
202*795d594fSAndroid Build Coastguard Worker   if (it != free_by_start_.end()) {
203*795d594fSAndroid Build Coastguard Worker     CHECK_LE(chunk.End(), it->Start());
204*795d594fSAndroid Build Coastguard Worker     if (chunk.End() == it->Start()) {
205*795d594fSAndroid Build Coastguard Worker       // Merge *it with this chunk.
206*795d594fSAndroid Build Coastguard Worker       chunk.size += it->size;
207*795d594fSAndroid Build Coastguard Worker       auto erase_pos = free_by_size_.find(FreeBySizeEntry { it->size, it });
208*795d594fSAndroid Build Coastguard Worker       DCHECK(erase_pos != free_by_size_.end());
209*795d594fSAndroid Build Coastguard Worker       RemoveChunk(erase_pos);
210*795d594fSAndroid Build Coastguard Worker       // "it" is invalidated but we don't need it anymore.
211*795d594fSAndroid Build Coastguard Worker     }
212*795d594fSAndroid Build Coastguard Worker   }
213*795d594fSAndroid Build Coastguard Worker   InsertChunk(chunk);
214*795d594fSAndroid Build Coastguard Worker 
215*795d594fSAndroid Build Coastguard Worker   if (kCheckFreeMaps) {
216*795d594fSAndroid Build Coastguard Worker     size_t free_after = CollectFree(free_by_start_, free_by_size_);
217*795d594fSAndroid Build Coastguard Worker 
218*795d594fSAndroid Build Coastguard Worker     if (free_after != free_before + size) {
219*795d594fSAndroid Build Coastguard Worker       DumpFreeMap(free_by_size_);
220*795d594fSAndroid Build Coastguard Worker       CHECK_EQ(free_after, free_before + size) << "Should be " << size << " difference from " << free_before;
221*795d594fSAndroid Build Coastguard Worker     }
222*795d594fSAndroid Build Coastguard Worker   }
223*795d594fSAndroid Build Coastguard Worker }
224*795d594fSAndroid Build Coastguard Worker 
225*795d594fSAndroid Build Coastguard Worker }  // namespace art
226