1 /* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef COMMON_COMPONENTS_COMMON_PAGEALLOCATOR_H 17 #define COMMON_COMPONENTS_COMMON_PAGEALLOCATOR_H 18 19 #include <pthread.h> 20 #if defined(__linux__) || defined(PANDA_TARGET_OHOS) || defined(__APPLE__) 21 #include <sys/mman.h> 22 #endif 23 #include <atomic> 24 #include <cstdint> 25 #include <mutex> 26 27 #include "common_components/base/globals.h" 28 #include "common_components/common/run_type.h" 29 #include "common_components/common/page_pool.h" 30 #include "common_components/log/log.h" 31 32 namespace common { 33 // when there is a need to use PageAllocator to manage 34 // the memory for a specific data structure, please add 35 // a new type 36 enum AllocationTag : uint32_t { 37 // alllocation type for std container 38 FINALIZER_PROCESSOR, // manage the std container in FinalizerProcessor 39 ALLOCATOR, // for Allocator 40 MUTATOR_LIST, // for mutator list 41 GC_WORK_STACK, // for gc mark and write barrier 42 GC_TASK_QUEUE, // for gc task queue 43 STACK_PTR, // for stack in stack_grow 44 // more to come 45 MAX_ALLOCATION_TAG 46 }; 47 48 // constants and utility function 49 class AllocatorUtils { 50 public: 51 AllocatorUtils() = delete; 52 ~AllocatorUtils() = delete; 53 NO_COPY_SEMANTIC_CC(AllocatorUtils); 54 NO_MOVE_SEMANTIC_CC(AllocatorUtils); 55 static const size_t ALLOC_PAGE_SIZE; 56 static constexpr uint32_t LOG_ALLOC_ALIGNMENT = 3; 57 static constexpr uint32_t ALLOC_ALIGNMENT = 1 << LOG_ALLOC_ALIGNMENT; 58 }; 59 60 // Allocator manages page allocation and deallocation 61 class PageAllocator { 62 // slots in a page are managed as a linked list 63 struct Slot { 64 Slot* next = nullptr; 65 }; 66 67 // pages are linked to each other as a double-linked list. 68 // the free slot list and other infomation are also in 69 // page header 70 class Page { 71 public: 72 // get a slot from the free slot list Allocate()73 inline void* Allocate() 74 { 75 void* result = nullptr; 76 if (header_ != nullptr) { 77 result = reinterpret_cast<void*>(header_); 78 header_ = header_->next; 79 --free_; 80 } 81 return result; 82 } 83 84 // return a slot to the free slot list Deallocate(void * slot)85 inline void Deallocate(void* slot) 86 { 87 Slot* cur = reinterpret_cast<Slot*>(slot); 88 cur->next = header_; 89 header_ = cur; 90 ++free_; 91 } 92 Available()93 inline bool Available() const { return free_ != 0; } 94 Empty()95 inline bool Empty() const { return free_ == total_; } 96 97 private: 98 Page* prev_ = nullptr; 99 Page* next_ = nullptr; 100 Slot* header_ = nullptr; 101 uint16_t free_ = 0; 102 uint16_t total_ = 0; 103 104 friend class PageAllocator; 105 }; 106 107 public: PageAllocator()108 PageAllocator() : nonFull_(nullptr), totalPages_(0), slotSize_(0), slotAlignment_(0) {} 109 PageAllocator(uint16_t size)110 explicit PageAllocator(uint16_t size) : nonFull_(nullptr), totalPages_(0), slotSize_(size) 111 { 112 slotAlignment_ = AlignUp<uint16_t>(size, AllocatorUtils::ALLOC_ALIGNMENT); 113 } 114 115 ~PageAllocator() = default; 116 Destroy()117 void Destroy() { DestroyList(nonFull_); } 118 Init(uint16_t size)119 void Init(uint16_t size) 120 { 121 slotSize_ = size; 122 slotAlignment_ = AlignUp<uint16_t>(size, AllocatorUtils::ALLOC_ALIGNMENT); 123 } 124 125 // allocation entrypoint Allocate()126 void* Allocate() 127 { 128 void* result = nullptr; 129 { 130 std::lock_guard<std::mutex> guard(allocLock_); 131 132 // create page if nonFull_ is nullptr 133 if (nonFull_ == nullptr) { 134 Page* cur = CreatePage(); 135 DCHECK_CC(cur != nullptr); 136 InitPage(*cur); 137 ++totalPages_; 138 nonFull_ = cur; 139 VLOG(DEBUG, "\ttotal pages mapped: %u, slot_size: %u", totalPages_, slotSize_); 140 } 141 142 result = nonFull_->Allocate(); 143 144 if (!(nonFull_->Available())) { 145 // move from nonFull to full 146 Page* cur = nonFull_; 147 RemoveFromList(nonFull_, *cur); 148 } 149 } 150 if (result != nullptr) { 151 LOGF_CHECK(memset_s(result, slotSize_, 0, slotSize_) == EOK) << "memset_s fail"; 152 } 153 return result; 154 } 155 156 // deallocation entrypoint Deallocate(void * slot)157 void Deallocate(void* slot) 158 { 159 Page* current = reinterpret_cast<Page*>( 160 AlignDown<uintptr_t>(reinterpret_cast<uintptr_t>(slot), AllocatorUtils::ALLOC_PAGE_SIZE)); 161 162 std::lock_guard<std::mutex> lockGuard(allocLock_); 163 // Transition from full to non-full state if the current resource is unavailable. 164 if (!current->Available()) { 165 AddToList(nonFull_, *current); 166 } 167 current->Deallocate(slot); 168 if (current->Empty()) { 169 RemoveFromList(nonFull_, *current); 170 DestroyPage(*current); 171 --totalPages_; 172 VLOG(DEBUG, "\ttotal pages mapped: %u, slot_size: %u", totalPages_, slotSize_); 173 } 174 } 175 176 private: 177 // get a page from os CreatePage()178 static inline Page* CreatePage() { return reinterpret_cast<Page*>(PagePool::Instance().GetPage()); } 179 180 // return the page to os DestroyPage(Page & page)181 static inline void DestroyPage(Page& page) 182 { 183 LOGF_CHECK(page.free_ == page.total_) << "\t destroy page in use: total = " << 184 page.total_ << ", free = " << page.free_; 185 DLOG(ALLOC, "\t destroy page %p total = %u, free = %u", &page, page.total_, page.free_); 186 PagePool::Instance().ReturnPage(reinterpret_cast<uint8_t*>(&page)); 187 } 188 189 // construct the data structure of a new allocated page InitPage(Page & page)190 void InitPage(Page& page) 191 { 192 page.prev_ = nullptr; 193 page.next_ = nullptr; 194 constexpr uint32_t offset = AlignUp<uint32_t>(sizeof(Page), AllocatorUtils::ALLOC_ALIGNMENT); 195 page.free_ = (AllocatorUtils::ALLOC_PAGE_SIZE - offset) / slotAlignment_; 196 page.total_ = page.free_; 197 LOGF_CHECK(page.free_ >= 1) << "use the wrong allocator! slot size = " << slotAlignment_; 198 199 char* start{ reinterpret_cast<char*>(&page) }; 200 char* slot{ start + offset }; 201 page.header_ = reinterpret_cast<Slot*>(slot); 202 Slot* prevSlot{ page.header_ }; 203 char* end{ start + AllocatorUtils::ALLOC_PAGE_SIZE - 1 }; 204 while (true) { 205 slot += slotAlignment_; 206 char* slotEnd{ slot + slotAlignment_ - 1 }; 207 if (slotEnd > end) { 208 break; 209 } 210 211 Slot* cur{ reinterpret_cast<Slot*>(slot) }; 212 prevSlot->next = cur; 213 prevSlot = cur; 214 } 215 216 DLOG(ALLOC, 217 "new page start = %p, end = %p, slot header = %p, total slots = %u, slot size = %u, sizeof(Page) = %u", 218 start, end, page.header_, page.total_, slotAlignment_, sizeof(Page)); 219 } 220 221 // linked-list management AddToList(Page * & list,Page & page)222 inline void AddToList(Page*& list, Page& page) const 223 { 224 if (list != nullptr) { 225 list->prev_ = &page; 226 } 227 page.next_ = list; 228 list = &page; 229 } 230 RemoveFromList(Page * & list,Page & page)231 inline void RemoveFromList(Page*& list, Page& page) const 232 { 233 Page* prev = page.prev_; 234 Page* next = page.next_; 235 if (&page == list) { 236 list = next; 237 if (next != nullptr) { 238 next->prev_ = nullptr; 239 } 240 } else { 241 prev->next_ = next; 242 if (next != nullptr) { 243 next->prev_ = prev; 244 } 245 } 246 page.next_ = nullptr; 247 page.prev_ = nullptr; 248 } 249 DestroyList(Page * & linkedList)250 inline void DestroyList(Page*& linkedList) 251 { 252 Page* current{ nullptr }; 253 while (linkedList != nullptr) { 254 current = linkedList; 255 linkedList = linkedList->next_; 256 DestroyPage(*current); 257 } 258 } 259 260 Page* nonFull_; 261 std::mutex allocLock_; 262 uint32_t totalPages_; 263 uint16_t slotSize_; 264 uint16_t slotAlignment_; 265 }; 266 267 // Utility class used for StdContainerAllocator 268 // It has lots of PageAllocators, each for different slot size, 269 // so all allocation sizes can be handled by this bridge class. 270 class AggregateAllocator { 271 public: 272 static constexpr uint32_t MAX_ALLOCATORS = 53; 273 274 NO_INLINE_CC PUBLIC_API static AggregateAllocator& Instance(AllocationTag tag); 275 AggregateAllocator()276 AggregateAllocator() 277 { 278 for (uint32_t i = 0; i < MAX_ALLOCATORS; ++i) { 279 allocator_[i].Init(static_cast<uint16_t>(RUNTYPE_RUN_IDX_TO_SIZE(i))); 280 } 281 } 282 ~AggregateAllocator() = default; 283 284 // choose appropriate allocation to allocate Allocate(size_t size)285 void* Allocate(size_t size) 286 { 287 uint32_t alignedSize = AlignUp(static_cast<uint32_t>(size), AllocatorUtils::ALLOC_ALIGNMENT); 288 if (alignedSize <= RUN_ALLOC_LARGE_SIZE) { 289 uint32_t index = RUNTYPE_SIZE_TO_RUN_IDX(alignedSize); 290 return allocator_[index].Allocate(); 291 } else { 292 return PagePool::Instance().GetPage(size); 293 } 294 } 295 Deallocate(void * p,size_t size)296 NO_INLINE_CC void Deallocate(void* p, size_t size) 297 { 298 uint32_t alignedSize = AlignUp(static_cast<uint32_t>(size), AllocatorUtils::ALLOC_ALIGNMENT); 299 if (alignedSize <= RUN_ALLOC_LARGE_SIZE) { 300 uint32_t index = RUNTYPE_SIZE_TO_RUN_IDX(alignedSize); 301 allocator_[index].Deallocate(p); 302 } else { 303 PagePool::Instance().ReturnPage(reinterpret_cast<uint8_t*>(p), size); 304 } 305 } 306 307 private: 308 PageAllocator allocator_[MAX_ALLOCATORS]; 309 }; 310 311 // Allocator is used to take control of memory allocation for std containers. 312 // It uses AggregateAllocator to dispatch the memory operation to appropriate PageAllocator. 313 template<class T, AllocationTag Cat> 314 class StdContainerAllocator { 315 public: 316 using const_pointer = const T*; 317 using const_reference = const T&; 318 using difference_type = ptrdiff_t; 319 using pointer = T*; 320 using reference = T&; 321 using size_type = size_t; 322 using value_type = T; 323 324 using propagate_on_container_swap = std::true_type; 325 using propagate_on_container_copy_assignment = std::false_type; 326 using propagate_on_container_move_assignment = std::true_type; 327 328 template<class U> 329 struct rebind { 330 using other = StdContainerAllocator<U, Cat>; 331 }; 332 333 StdContainerAllocator() = default; 334 ~StdContainerAllocator() = default; 335 336 template<class U> StdContainerAllocator(const StdContainerAllocator<U,Cat> &)337 StdContainerAllocator(const StdContainerAllocator<U, Cat>&) 338 {} 339 StdContainerAllocator(const StdContainerAllocator<T,Cat> &)340 StdContainerAllocator(const StdContainerAllocator<T, Cat>&) {} 341 StdContainerAllocator(StdContainerAllocator<T,Cat> &&)342 StdContainerAllocator(StdContainerAllocator<T, Cat>&&) {} 343 344 StdContainerAllocator<T, Cat>& operator=(const StdContainerAllocator<T, Cat>&) { return *this; } 345 346 StdContainerAllocator<T, Cat>& operator=(StdContainerAllocator<T, Cat>&&) { return *this; } 347 address(reference x)348 pointer address(reference x) const { return std::addressof(x); } 349 address(const_reference x)350 const_pointer address(const_reference x) const { return std::addressof(x); } 351 352 pointer allocate(size_type n, const void* hint __attribute__((unused)) = 0) 353 { 354 pointer result = static_cast<pointer>(AggregateAllocator::Instance(Cat).Allocate(sizeof(T) * n)); 355 return result; 356 } 357 deallocate(pointer p,size_type n)358 void deallocate(pointer p, size_type n) { AggregateAllocator::Instance(Cat).Deallocate(p, sizeof(T) * n); } 359 max_size()360 size_type max_size() const { return static_cast<size_type>(~0) / sizeof(value_type); } 361 construct(pointer p,const_reference val)362 void construct(pointer p, const_reference val) { ::new (reinterpret_cast<void*>(p)) value_type(val); } 363 364 template<class Up, class... Args> construct(Up * p,Args &&...args)365 void construct(Up* p, Args&&... args) 366 { 367 ::new (reinterpret_cast<void*>(p)) Up(std::forward<Args>(args)...); 368 } 369 destroy(pointer p)370 void destroy(pointer p) { p->~value_type(); } 371 }; 372 // vector::swap requires that the allocator defined by ourselves should be comparable during compiling period, 373 // so we overload operator == and return true. 374 template<typename Tp, AllocationTag tag> 375 inline bool operator==(StdContainerAllocator<Tp, tag>&, StdContainerAllocator<Tp, tag>&) noexcept 376 { 377 return true; 378 } 379 } // namespace common 380 #endif 381