• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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