* 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 <pthread.h>
#if defined(__linux__) || defined(PANDA_TARGET_OHOS) || defined(__APPLE__)
#include <sys/mman.h>
#endif
#include <atomic>
#include <cstdint>
#include <mutex>
#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 {
enum AllocationTag : uint32_t {
FINALIZER_PROCESSOR,
ALLOCATOR,
MUTATOR_LIST,
GC_WORK_STACK,
GC_TASK_QUEUE,
STACK_PTR,
MAX_ALLOCATION_TAG
};
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;
};
class PageAllocator {
struct Slot {
Slot* next = nullptr;
};
class Page {
public:
inline void* Allocate()
{
void* result = nullptr;
if (header_ != nullptr) {
result = reinterpret_cast<void*>(header_);
header_ = header_->next;
--free_;
}
return result;
}
inline void Deallocate(void* slot)
{
Slot* cur = reinterpret_cast<Slot*>(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<uint16_t>(size, AllocatorUtils::ALLOC_ALIGNMENT);
}
~PageAllocator() = default;
void Destroy() { DestroyList(nonFull_); }
void Init(uint16_t size)
{
slotSize_ = size;
slotAlignment_ = AlignUp<uint16_t>(size, AllocatorUtils::ALLOC_ALIGNMENT);
}
void* Allocate()
{
void* result = nullptr;
{
std::lock_guard<std::mutex> guard(allocLock_);
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())) {
Page* cur = nonFull_;
RemoveFromList(nonFull_, *cur);
}
}
if (result != nullptr) {
LOGF_CHECK(memset_s(result, slotSize_, 0, slotSize_) == EOK) << "memset_s fail";
}
return result;
}
void Deallocate(void* slot)
{
Page* current = reinterpret_cast<Page*>(
AlignDown<uintptr_t>(reinterpret_cast<uintptr_t>(slot), AllocatorUtils::ALLOC_PAGE_SIZE));
std::lock_guard<std::mutex> lockGuard(allocLock_);
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:
static inline Page* CreatePage() { return reinterpret_cast<Page*>(PagePool::Instance().GetPage()); }
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<uint8_t*>(&page));
}
void InitPage(Page& page)
{
page.prev_ = nullptr;
page.next_ = nullptr;
constexpr uint32_t offset = AlignUp<uint32_t>(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<char*>(&page) };
char* slot{ start + offset };
page.header_ = reinterpret_cast<Slot*>(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*>(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));
}
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_;
};
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<uint16_t>(RUNTYPE_RUN_IDX_TO_SIZE(i)));
}
}
~AggregateAllocator() = default;
void* Allocate(size_t size)
{
uint32_t alignedSize = AlignUp(static_cast<uint32_t>(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<uint32_t>(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<uint8_t*>(p), size);
}
}
private:
PageAllocator allocator_[MAX_ALLOCATORS];
};
template<class T, AllocationTag Cat>
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<class U>
struct rebind {
using other = StdContainerAllocator<U, Cat>;
};
StdContainerAllocator() = default;
~StdContainerAllocator() = default;
template<class U>
StdContainerAllocator(const StdContainerAllocator<U, Cat>&)
{}
StdContainerAllocator(const StdContainerAllocator<T, Cat>&) {}
StdContainerAllocator(StdContainerAllocator<T, Cat>&&) {}
StdContainerAllocator<T, Cat>& operator=(const StdContainerAllocator<T, Cat>&) { return *this; }
StdContainerAllocator<T, Cat>& operator=(StdContainerAllocator<T, Cat>&&) { 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<pointer>(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<size_type>(~0) / sizeof(value_type); }
void construct(pointer p, const_reference val) { ::new (reinterpret_cast<void*>(p)) value_type(val); }
template<class Up, class... Args>
void construct(Up* p, Args&&... args)
{
::new (reinterpret_cast<void*>(p)) Up(std::forward<Args>(args)...);
}
void destroy(pointer p) { p->~value_type(); }
};
template<typename Tp, AllocationTag tag>
inline bool operator==(StdContainerAllocator<Tp, tag>&, StdContainerAllocator<Tp, tag>&) noexcept
{
return true;
}
}
#endif