diff --git a/README.md b/README.md index a7889cc..4348490 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,8 @@ The list of intentional deviations can be obtained by simply searching the codeb ### Next version WIP - Add optional trace events, enabled via the `O1HEAP_TRACE` build-time option. +- Add [`o1heapMinArenaSize`](https://github.com/pavel-kirienko/o1heap/issues/17#issuecomment-1518245157). +- Add [`o1heapGetMaxAllocationSize`](https://github.com/pavel-kirienko/o1heap/issues/18). ### v2.1 diff --git a/o1heap/o1heap.c b/o1heap/o1heap.c index 8be3101..6af213b 100644 --- a/o1heap/o1heap.c +++ b/o1heap/o1heap.c @@ -14,7 +14,7 @@ // Copyright (c) Pavel Kirienko // Authors: Pavel Kirienko -// ReSharper disable CppDFANullDereference +// ReSharper disable CppDFANullDereference CppRedundantCastExpression #include "o1heap.h" #include @@ -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); @@ -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); diff --git a/o1heap/o1heap.h b/o1heap/o1heap.h index 1b0a387..bec5866 100644 --- a/o1heap/o1heap.h +++ b/o1heap/o1heap.h @@ -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, @@ -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); @@ -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::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. diff --git a/tests/internal.hpp b/tests/internal.hpp index fb56342..8f645bf 100644 --- a/tests/internal.hpp +++ b/tests/internal.hpp @@ -167,6 +167,11 @@ struct O1HeapInstance final validate(); } + [[nodiscard]] auto getMaxAllocationSize() const + { + return o1heapGetMaxAllocationSize(reinterpret_cast(this)); + } + [[nodiscard]] auto doInvariantsHold() const { return o1heapDoInvariantsHold(reinterpret_cast(this)); diff --git a/tests/test_general.cpp b/tests/test_general.cpp index a6308f4..c02f14a 100644 --- a/tests/test_general.cpp +++ b/tests/test_general.cpp @@ -562,6 +562,34 @@ TEST_CASE("General: random A") } } +TEST_CASE("General: min arena size") +{ + alignas(128U) std::array arena{}; + REQUIRE(nullptr == init(arena.data(), o1heapMinArenaSize - 1U)); + REQUIRE(nullptr != init(arena.data(), o1heapMinArenaSize)); +} + +TEST_CASE("General: max allocation size") +{ + alignas(128U) std::array 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;