blob: d58685194aeb819ee98693af77e5d1bb3107a20f [file] [log] [blame]
//===-- 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 <stddef.h>
#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<cpp::byte> 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<cpp::byte> region() const { return {begin, end}; }
private:
void init();
void *allocate_impl(size_t alignment, size_t size);
span<cpp::byte> block_to_span(Block *block) {
return span<cpp::byte>(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 <size_t BUFF_SIZE> 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<cpp::byte *>(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<cpp::byte *>(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