Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions o1heap/o1heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// Copyright (c) Pavel Kirienko
// Authors: Pavel Kirienko <[email protected]>

// ReSharper disable CppDFANullDereference
// ReSharper disable CppDFANullDereference CppRedundantCastExpression

#include "o1heap.h"
#include <assert.h>
Expand Down Expand Up @@ -258,11 +258,12 @@ O1HEAP_PRIVATE void unbin(O1HeapInstance* const handle, const Fragment* const fr

// ---------------------------------------- PUBLIC API IMPLEMENTATION ----------------------------------------

const size_t o1heapMinArenaSize = INSTANCE_SIZE_PADDED + FRAGMENT_SIZE_MIN;

O1HeapInstance* o1heapInit(void* const base, const size_t size)
{
O1HeapInstance* out = NULL;
if ((base != NULL) && ((((size_t) base) % O1HEAP_ALIGNMENT) == 0U) &&
(size >= (INSTANCE_SIZE_PADDED + FRAGMENT_SIZE_MIN)))
if ((base != NULL) && ((((size_t) base) % O1HEAP_ALIGNMENT) == 0U) && (size >= o1heapMinArenaSize))
{
// Allocate the core heap metadata structure in the beginning of the arena.
O1HEAP_ASSERT(((size_t) base) % sizeof(O1HeapInstance*) == 0U);
Expand Down Expand Up @@ -476,6 +477,14 @@ void o1heapFree(O1HeapInstance* const handle, void* const pointer)
}
}

size_t o1heapGetMaxAllocationSize(const O1HeapInstance* const handle)
{
O1HEAP_ASSERT(handle != NULL);
// The largest allocation is smaller (up to almost two times) than the arena capacity,
// due to the power-of-two padding and the fragment header overhead.
return pow2(log2Floor(handle->diagnostics.capacity)) - O1HEAP_ALIGNMENT;
}

bool o1heapDoInvariantsHold(const O1HeapInstance* const handle)
{
O1HEAP_ASSERT(handle != NULL);
Expand Down
18 changes: 15 additions & 3 deletions o1heap/o1heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ typedef struct
uint64_t oom_count;
} O1HeapDiagnostics;

/// o1heapInit() will fail unless the arena size is at least this large.
/// This value depends only on the machine architecture.
/// The other reason to fail is if the arena pointer is not aligned at O1HEAP_ALIGNMENT.
extern const size_t o1heapMinArenaSize;

/// The arena base pointer shall be aligned at O1HEAP_ALIGNMENT, otherwise NULL is returned.
///
/// The total heap capacity cannot exceed approx. (SIZE_MAX/2). If the arena size allows for a larger heap,
Expand All @@ -76,10 +81,13 @@ typedef struct
/// own needs (normally about 40..600 bytes depending on the architecture, but this parameter is not characterized).
/// A pointer to the newly initialized instance is returned.
///
/// If the provided space is insufficient, NULL is returned.
/// The function fails and returns NULL iff:
/// - The provided space is less than o1heapMinArenaSize.
/// - The base pointer is not aligned at O1HEAP_ALIGNMENT.
/// - The base pointer is NULL.
///
/// An initialized instance does not hold any resources. Therefore, if the instance is no longer needed,
/// it can be discarded without any de-initialization procedures.
/// An initialized instance does not hold any resources except for the arena memory.
/// Therefore, if the instance is no longer needed, it can be discarded without any de-initialization procedures.
///
/// The heap is not thread-safe; external synchronization may be required.
O1HeapInstance* o1heapInit(void* const base, const size_t size);
Expand All @@ -104,6 +112,10 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount);
/// The function is executed in constant time.
void o1heapFree(O1HeapInstance* const handle, void* const pointer);

/// Obtains the maximum theoretically possible allocation size for this heap instance.
/// This is useful when implementing std::allocator_traits<Alloc>::max_size.
size_t o1heapGetMaxAllocationSize(const O1HeapInstance* const handle);

/// Performs a basic sanity check on the heap.
/// This function can be used as a weak but fast method of heap corruption detection.
/// If the handle pointer is NULL, the behavior is undefined.
Expand Down
5 changes: 5 additions & 0 deletions tests/internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ struct O1HeapInstance final
validate();
}

[[nodiscard]] auto getMaxAllocationSize() const
{
return o1heapGetMaxAllocationSize(reinterpret_cast<const ::O1HeapInstance*>(this));
}

[[nodiscard]] auto doInvariantsHold() const
{
return o1heapDoInvariantsHold(reinterpret_cast<const ::O1HeapInstance*>(this));
Expand Down
28 changes: 28 additions & 0 deletions tests/test_general.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,34 @@ TEST_CASE("General: random A")
}
}

TEST_CASE("General: min arena size")
{
alignas(128U) std::array<std::byte, 1024> arena{};
REQUIRE(nullptr == init(arena.data(), o1heapMinArenaSize - 1U));
REQUIRE(nullptr != init(arena.data(), o1heapMinArenaSize));
}

TEST_CASE("General: max allocation size")
{
alignas(128U) std::array<std::byte, 4096U + sizeof(internal::O1HeapInstance) + O1HEAP_ALIGNMENT - 1U> arena{};
{
auto heap = init(arena.data(), std::size(arena));
REQUIRE(heap != nullptr);
REQUIRE(heap->diagnostics.capacity == 4096);
REQUIRE(4096 - O1HEAP_ALIGNMENT == heap->getMaxAllocationSize());
REQUIRE(nullptr == heap->allocate(heap->getMaxAllocationSize() + 1));
REQUIRE(nullptr != heap->allocate(heap->getMaxAllocationSize() + 0));
}
{
auto heap = init(arena.data(), std::size(arena) - O1HEAP_ALIGNMENT);
REQUIRE(heap != nullptr);
REQUIRE(heap->diagnostics.capacity < 4095);
REQUIRE(2048 - O1HEAP_ALIGNMENT == heap->getMaxAllocationSize());
REQUIRE(nullptr == heap->allocate(heap->getMaxAllocationSize() + 1));
REQUIRE(nullptr != heap->allocate(heap->getMaxAllocationSize() + 0));
}
}

TEST_CASE("General: invariant checker")
{
using internal::Fragment;
Expand Down
Loading