//===-- Interface for freelist_heap ---------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H #define LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H #include #include "block.h" #include "freestore.h" #include "src/__support/CPP/optional.h" #include "src/__support/CPP/span.h" #include "src/__support/libc_assert.h" #include "src/__support/macros/config.h" #include "src/__support/math_extras.h" #include "src/string/memory_utils/inline_memcpy.h" #include "src/string/memory_utils/inline_memset.h" namespace LIBC_NAMESPACE_DECL { extern "C" cpp::byte _end; extern "C" cpp::byte __llvm_libc_heap_limit; using cpp::optional; using cpp::span; LIBC_INLINE constexpr bool IsPow2(size_t x) { return x && (x & (x - 1)) == 0; } class FreeListHeap { public: constexpr FreeListHeap() : begin(&_end), end(&__llvm_libc_heap_limit) {} constexpr FreeListHeap(span region) : begin(region.begin()), end(region.end()) {} void *allocate(size_t size); void *aligned_allocate(size_t alignment, size_t size); // NOTE: All pointers passed to free must come from one of the other // allocation functions: `allocate`, `aligned_allocate`, `realloc`, `calloc`. void free(void *ptr); void *realloc(void *ptr, size_t size); void *calloc(size_t num, size_t size); cpp::span region() const { return {begin, end}; } private: void init(); void *allocate_impl(size_t alignment, size_t size); span block_to_span(Block *block) { return span(block->usable_space(), block->inner_size()); } bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; } cpp::byte *begin; cpp::byte *end; bool is_initialized = false; FreeStore free_store; }; template class FreeListHeapBuffer : public FreeListHeap { public: constexpr FreeListHeapBuffer() : FreeListHeap{buffer}, buffer{} {} private: cpp::byte buffer[BUFF_SIZE]; }; LIBC_INLINE void FreeListHeap::init() { LIBC_ASSERT(!is_initialized && "duplicate initialization"); auto result = Block::init(region()); Block *block = *result; free_store.set_range({0, cpp::bit_ceil(block->inner_size())}); free_store.insert(block); is_initialized = true; } LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) { if (size == 0) return nullptr; if (!is_initialized) init(); size_t request_size = Block::min_size_for_allocation(alignment, size); if (!request_size) return nullptr; Block *block = free_store.remove_best_fit(request_size); if (!block) return nullptr; auto block_info = Block::allocate(block, alignment, size); if (block_info.next) free_store.insert(block_info.next); if (block_info.prev) free_store.insert(block_info.prev); block_info.block->mark_used(); return block_info.block->usable_space(); } LIBC_INLINE void *FreeListHeap::allocate(size_t size) { return allocate_impl(alignof(max_align_t), size); } LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment, size_t size) { // The alignment must be an integral power of two. if (!IsPow2(alignment)) return nullptr; // The size parameter must be an integral multiple of alignment. if (size % alignment != 0) return nullptr; // The minimum alignment supported by Block is max_align_t. alignment = cpp::max(alignment, alignof(max_align_t)); return allocate_impl(alignment, size); } LIBC_INLINE void FreeListHeap::free(void *ptr) { cpp::byte *bytes = static_cast(ptr); LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer"); Block *block = Block::from_usable_space(bytes); LIBC_ASSERT(block->next() && "sentinel last block cannot be freed"); LIBC_ASSERT(block->used() && "double free"); block->mark_free(); // Can we combine with the left or right blocks? Block *prev_free = block->prev_free(); Block *next = block->next(); if (prev_free != nullptr) { // Remove from free store and merge. free_store.remove(prev_free); block = prev_free; block->merge_next(); } if (!next->used()) { free_store.remove(next); block->merge_next(); } // Add back to the freelist free_store.insert(block); } // Follows constract of the C standard realloc() function // If ptr is free'd, will return nullptr. LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) { if (size == 0) { free(ptr); return nullptr; } // If the pointer is nullptr, allocate a new memory. if (ptr == nullptr) return allocate(size); cpp::byte *bytes = static_cast(ptr); if (!is_valid_ptr(bytes)) return nullptr; Block *block = Block::from_usable_space(bytes); if (!block->used()) return nullptr; size_t old_size = block->inner_size(); // Do nothing and return ptr if the required memory size is smaller than // the current size. if (old_size >= size) return ptr; void *new_ptr = allocate(size); // Don't invalidate ptr if allocate(size) fails to initilize the memory. if (new_ptr == nullptr) return nullptr; LIBC_NAMESPACE::inline_memcpy(new_ptr, ptr, old_size); free(ptr); return new_ptr; } LIBC_INLINE void *FreeListHeap::calloc(size_t num, size_t size) { size_t bytes; if (__builtin_mul_overflow(num, size, &bytes)) return nullptr; void *ptr = allocate(bytes); if (ptr != nullptr) LIBC_NAMESPACE::inline_memset(ptr, 0, bytes); return ptr; } extern FreeListHeap *freelist_heap; } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H