/* * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef COMMON_COMPONENTS_COMMON_PAGEALLOCATOR_H #define COMMON_COMPONENTS_COMMON_PAGEALLOCATOR_H #include #if defined(__linux__) || defined(PANDA_TARGET_OHOS) || defined(__APPLE__) #include #endif #include #include #include #include "common_components/base/globals.h" #include "common_components/common/run_type.h" #include "common_components/common/page_pool.h" #include "common_components/log/log.h" namespace common { // when there is a need to use PageAllocator to manage // the memory for a specific data structure, please add // a new type enum AllocationTag : uint32_t { // alllocation type for std container FINALIZER_PROCESSOR, // manage the std container in FinalizerProcessor ALLOCATOR, // for Allocator MUTATOR_LIST, // for mutator list GC_WORK_STACK, // for gc mark and write barrier GC_TASK_QUEUE, // for gc task queue STACK_PTR, // for stack in stack_grow // more to come MAX_ALLOCATION_TAG }; // constants and utility function class AllocatorUtils { public: AllocatorUtils() = delete; ~AllocatorUtils() = delete; NO_COPY_SEMANTIC_CC(AllocatorUtils); NO_MOVE_SEMANTIC_CC(AllocatorUtils); static const size_t ALLOC_PAGE_SIZE; static constexpr uint32_t LOG_ALLOC_ALIGNMENT = 3; static constexpr uint32_t ALLOC_ALIGNMENT = 1 << LOG_ALLOC_ALIGNMENT; }; // Allocator manages page allocation and deallocation class PageAllocator { // slots in a page are managed as a linked list struct Slot { Slot* next = nullptr; }; // pages are linked to each other as a double-linked list. // the free slot list and other infomation are also in // page header class Page { public: // get a slot from the free slot list inline void* Allocate() { void* result = nullptr; if (header_ != nullptr) { result = reinterpret_cast(header_); header_ = header_->next; --free_; } return result; } // return a slot to the free slot list inline void Deallocate(void* slot) { Slot* cur = reinterpret_cast(slot); cur->next = header_; header_ = cur; ++free_; } inline bool Available() const { return free_ != 0; } inline bool Empty() const { return free_ == total_; } private: Page* prev_ = nullptr; Page* next_ = nullptr; Slot* header_ = nullptr; uint16_t free_ = 0; uint16_t total_ = 0; friend class PageAllocator; }; public: PageAllocator() : nonFull_(nullptr), totalPages_(0), slotSize_(0), slotAlignment_(0) {} explicit PageAllocator(uint16_t size) : nonFull_(nullptr), totalPages_(0), slotSize_(size) { slotAlignment_ = AlignUp(size, AllocatorUtils::ALLOC_ALIGNMENT); } ~PageAllocator() = default; void Destroy() { DestroyList(nonFull_); } void Init(uint16_t size) { slotSize_ = size; slotAlignment_ = AlignUp(size, AllocatorUtils::ALLOC_ALIGNMENT); } // allocation entrypoint void* Allocate() { void* result = nullptr; { std::lock_guard guard(allocLock_); // create page if nonFull_ is nullptr if (nonFull_ == nullptr) { Page* cur = CreatePage(); DCHECK_CC(cur != nullptr); InitPage(*cur); ++totalPages_; nonFull_ = cur; VLOG(DEBUG, "\ttotal pages mapped: %u, slot_size: %u", totalPages_, slotSize_); } result = nonFull_->Allocate(); if (!(nonFull_->Available())) { // move from nonFull to full Page* cur = nonFull_; RemoveFromList(nonFull_, *cur); } } if (result != nullptr) { LOGF_CHECK(memset_s(result, slotSize_, 0, slotSize_) == EOK) << "memset_s fail"; } return result; } // deallocation entrypoint void Deallocate(void* slot) { Page* current = reinterpret_cast( AlignDown(reinterpret_cast(slot), AllocatorUtils::ALLOC_PAGE_SIZE)); std::lock_guard lockGuard(allocLock_); // Transition from full to non-full state if the current resource is unavailable. if (!current->Available()) { AddToList(nonFull_, *current); } current->Deallocate(slot); if (current->Empty()) { RemoveFromList(nonFull_, *current); DestroyPage(*current); --totalPages_; VLOG(DEBUG, "\ttotal pages mapped: %u, slot_size: %u", totalPages_, slotSize_); } } private: // get a page from os static inline Page* CreatePage() { return reinterpret_cast(PagePool::Instance().GetPage()); } // return the page to os static inline void DestroyPage(Page& page) { LOGF_CHECK(page.free_ == page.total_) << "\t destroy page in use: total = " << page.total_ << ", free = " << page.free_; DLOG(ALLOC, "\t destroy page %p total = %u, free = %u", &page, page.total_, page.free_); PagePool::Instance().ReturnPage(reinterpret_cast(&page)); } // construct the data structure of a new allocated page void InitPage(Page& page) { page.prev_ = nullptr; page.next_ = nullptr; constexpr uint32_t offset = AlignUp(sizeof(Page), AllocatorUtils::ALLOC_ALIGNMENT); page.free_ = (AllocatorUtils::ALLOC_PAGE_SIZE - offset) / slotAlignment_; page.total_ = page.free_; LOGF_CHECK(page.free_ >= 1) << "use the wrong allocator! slot size = " << slotAlignment_; char* start{ reinterpret_cast(&page) }; char* slot{ start + offset }; page.header_ = reinterpret_cast(slot); Slot* prevSlot{ page.header_ }; char* end{ start + AllocatorUtils::ALLOC_PAGE_SIZE - 1 }; while (true) { slot += slotAlignment_; char* slotEnd{ slot + slotAlignment_ - 1 }; if (slotEnd > end) { break; } Slot* cur{ reinterpret_cast(slot) }; prevSlot->next = cur; prevSlot = cur; } DLOG(ALLOC, "new page start = %p, end = %p, slot header = %p, total slots = %u, slot size = %u, sizeof(Page) = %u", start, end, page.header_, page.total_, slotAlignment_, sizeof(Page)); } // linked-list management inline void AddToList(Page*& list, Page& page) const { if (list != nullptr) { list->prev_ = &page; } page.next_ = list; list = &page; } inline void RemoveFromList(Page*& list, Page& page) const { Page* prev = page.prev_; Page* next = page.next_; if (&page == list) { list = next; if (next != nullptr) { next->prev_ = nullptr; } } else { prev->next_ = next; if (next != nullptr) { next->prev_ = prev; } } page.next_ = nullptr; page.prev_ = nullptr; } inline void DestroyList(Page*& linkedList) { Page* current{ nullptr }; while (linkedList != nullptr) { current = linkedList; linkedList = linkedList->next_; DestroyPage(*current); } } Page* nonFull_; std::mutex allocLock_; uint32_t totalPages_; uint16_t slotSize_; uint16_t slotAlignment_; }; // Utility class used for StdContainerAllocator // It has lots of PageAllocators, each for different slot size, // so all allocation sizes can be handled by this bridge class. class AggregateAllocator { public: static constexpr uint32_t MAX_ALLOCATORS = 53; NO_INLINE_CC PUBLIC_API static AggregateAllocator& Instance(AllocationTag tag); AggregateAllocator() { for (uint32_t i = 0; i < MAX_ALLOCATORS; ++i) { allocator_[i].Init(static_cast(RUNTYPE_RUN_IDX_TO_SIZE(i))); } } ~AggregateAllocator() = default; // choose appropriate allocation to allocate void* Allocate(size_t size) { uint32_t alignedSize = AlignUp(static_cast(size), AllocatorUtils::ALLOC_ALIGNMENT); if (alignedSize <= RUN_ALLOC_LARGE_SIZE) { uint32_t index = RUNTYPE_SIZE_TO_RUN_IDX(alignedSize); return allocator_[index].Allocate(); } else { return PagePool::Instance().GetPage(size); } } NO_INLINE_CC void Deallocate(void* p, size_t size) { uint32_t alignedSize = AlignUp(static_cast(size), AllocatorUtils::ALLOC_ALIGNMENT); if (alignedSize <= RUN_ALLOC_LARGE_SIZE) { uint32_t index = RUNTYPE_SIZE_TO_RUN_IDX(alignedSize); allocator_[index].Deallocate(p); } else { PagePool::Instance().ReturnPage(reinterpret_cast(p), size); } } private: PageAllocator allocator_[MAX_ALLOCATORS]; }; // Allocator is used to take control of memory allocation for std containers. // It uses AggregateAllocator to dispatch the memory operation to appropriate PageAllocator. template class StdContainerAllocator { public: using const_pointer = const T*; using const_reference = const T&; using difference_type = ptrdiff_t; using pointer = T*; using reference = T&; using size_type = size_t; using value_type = T; using propagate_on_container_swap = std::true_type; using propagate_on_container_copy_assignment = std::false_type; using propagate_on_container_move_assignment = std::true_type; template struct rebind { using other = StdContainerAllocator; }; StdContainerAllocator() = default; ~StdContainerAllocator() = default; template StdContainerAllocator(const StdContainerAllocator&) {} StdContainerAllocator(const StdContainerAllocator&) {} StdContainerAllocator(StdContainerAllocator&&) {} StdContainerAllocator& operator=(const StdContainerAllocator&) { return *this; } StdContainerAllocator& operator=(StdContainerAllocator&&) { return *this; } pointer address(reference x) const { return std::addressof(x); } const_pointer address(const_reference x) const { return std::addressof(x); } pointer allocate(size_type n, const void* hint __attribute__((unused)) = 0) { pointer result = static_cast(AggregateAllocator::Instance(Cat).Allocate(sizeof(T) * n)); return result; } void deallocate(pointer p, size_type n) { AggregateAllocator::Instance(Cat).Deallocate(p, sizeof(T) * n); } size_type max_size() const { return static_cast(~0) / sizeof(value_type); } void construct(pointer p, const_reference val) { ::new (reinterpret_cast(p)) value_type(val); } template void construct(Up* p, Args&&... args) { ::new (reinterpret_cast(p)) Up(std::forward(args)...); } void destroy(pointer p) { p->~value_type(); } }; // vector::swap requires that the allocator defined by ourselves should be comparable during compiling period, // so we overload operator == and return true. template inline bool operator==(StdContainerAllocator&, StdContainerAllocator&) noexcept { return true; } } // namespace common #endif