From 8f982a15d57dac967600f7d945944d92024d2506 Mon Sep 17 00:00:00 2001 From: Jamie Reece Wilson Date: Sun, 7 Jan 2024 04:08:06 +0000 Subject: [PATCH] [*] Remove critical section APIs while keeping the original thread-safe implementation using rt futexes [*] amend port: f30d5203 --- o1heap.cpp | 74 +++++++++++++++++++++--------------------------------- o1heap.hpp | 10 +------- 2 files changed, 29 insertions(+), 55 deletions(-) diff --git a/o1heap.cpp b/o1heap.cpp index 03ccbba..043d3b0 100644 --- a/o1heap.cpp +++ b/o1heap.cpp @@ -115,8 +115,7 @@ struct O1HeapInstance Fragment *bins[NUM_BINS_MAX]; ///< Smallest fragments are in the bin at index 0. size_t nonempty_bin_mask; ///< Bit 1 represents a non-empty bin; bin at index 0 is for the smallest fragments. - O1HeapHook critical_section_enter; - O1HeapHook critical_section_leave; + AuFutexMutex mutex; O1HeapDiagnostics diagnostics; }; @@ -141,22 +140,23 @@ O1HEAP_PRIVATE uint8_t log2Floor(const size_t x) O1HEAP_ASSERT(x > 0); AuUInt8 index {}; AuBitScanReverse(index, x); - return (uint8_t)(((sizeof(x) * CHAR_BIT) - 1U) - index); + return index; } O1HEAP_PRIVATE uint_fast8_t log2Ceil(const size_t x) { AuUInt8 index {}; - AuBitScanReverse(index, x - 1); - return (x <= 1U) ? 0U : (uint_fast8_t)((sizeof(x) * CHAR_BIT) - (index)); + AuBitScanReverse(index, x); + if (size_t(1) << size_t(index) == x) + { + return index; + } + return (x <= 1U) ? 0U : index + 1; } O1HEAP_PRIVATE size_t roundUpToPowerOf2(const size_t x) { - O1HEAP_ASSERT(x >= 2U); - AuUInt8 index {}; - AuBitScanReverse(index, x - 1); - return ((size_t)1U) << ((sizeof(x) * CHAR_BIT) - (index)); + return AuRoundUpPow2(x); } /// Raise 2 into the specified power. @@ -168,15 +168,6 @@ O1HEAP_PRIVATE size_t pow2(const uint8_t power) return ((size_t)1U) << power; } -O1HEAP_PRIVATE void invoke(const O1HeapInstance *const handle, const O1HeapHook &hook); -O1HEAP_PRIVATE void invoke(const O1HeapInstance *const handle, const O1HeapHook &hook) -{ - if (hook != NULL) - { - hook(handle); - } -} - /// Links two fragments so that their next/prev pointers point to each other; left goes before right. O1HEAP_PRIVATE void interlink(Fragment *const left, Fragment *const right); O1HEAP_PRIVATE void interlink(Fragment *const left, Fragment *const right) @@ -247,9 +238,7 @@ O1HEAP_PRIVATE void unbin(O1HeapInstance *const handle, const Fragment *const fr // ---------------------------------------- PUBLIC API IMPLEMENTATION ---------------------------------------- O1HeapInstance *o1heapInit(void *const base, - const size_t size, - O1HeapHook critical_section_enter, - O1HeapHook critical_section_leave) + const size_t size) { O1HeapInstance *out = NULL; if ((base != NULL) && ((((size_t)base) % O1HEAP_ALIGNMENT) == 0U) && @@ -262,8 +251,6 @@ O1HeapInstance *o1heapInit(void *const base, out = (O1HeapInstance *)base; out->nonempty_bin_mask = 0U; - out->critical_section_enter = critical_section_enter; - out->critical_section_leave = critical_section_leave; for (size_t i = 0; i < NUM_BINS_MAX; i++) { out->bins[i] = NULL; @@ -329,7 +316,7 @@ void *o1heapAllocate(O1HeapInstance *const handle, const size_t amount) O1HEAP_ASSERT(optimal_bin_index < NUM_BINS_MAX); const size_t candidate_bin_mask = ~(pow2(optimal_bin_index) - 1U); - invoke(handle, handle->critical_section_enter); + AU_LOCK_GUARD(handle->mutex); // Find the smallest non-empty bin we can use. const size_t suitable_bins = handle->nonempty_bin_mask & candidate_bin_mask; @@ -381,30 +368,30 @@ void *o1heapAllocate(O1HeapInstance *const handle, const size_t amount) out = ((uint8_t *)frag) + O1HEAP_ALIGNMENT; } } - else - { - invoke(handle, handle->critical_section_enter); - } // Update the diagnostics. - if (O1HEAP_LIKELY(handle->diagnostics.peak_request_size < amount)) + size_t uOld; + do { - handle->diagnostics.peak_request_size = amount; + uOld = AuAtomicLoad(&handle->diagnostics.peak_request_size); + if (uOld < amount) + { + if (AuAtomicCompareExchange(&handle->diagnostics.peak_request_size, amount, uOld) == uOld) + { + break; + } + } } - if (O1HEAP_LIKELY((out == NULL) && (amount > 0U))) + while (uOld < amount); + + if ((out == NULL) && (amount > 0U)) { - handle->diagnostics.oom_count++; + AuAtomicAdd(&handle->diagnostics.oom_count, uint64_t(1)); } - invoke(handle, handle->critical_section_leave); return out; } -void o1HeapReleaseCpp(O1HeapInstance *handle) -{ - handle->~O1HeapInstance(); -} - void o1heapFree(O1HeapInstance *const handle, void *const pointer) { O1HEAP_ASSERT(handle != NULL); @@ -425,7 +412,7 @@ void o1heapFree(O1HeapInstance *const handle, void *const pointer) O1HEAP_ASSERT(frag->header.size <= handle->diagnostics.capacity); O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U); - invoke(handle, handle->critical_section_enter); + AU_LOCK_GUARD(handle->mutex); // Even if we're going to drop the fragment later, mark it free anyway to prevent double-free. frag->header.used = false; @@ -472,8 +459,6 @@ void o1heapFree(O1HeapInstance *const handle, void *const pointer) { rebin(handle, frag); } - - invoke(handle, handle->critical_section_leave); } } @@ -482,7 +467,7 @@ bool o1heapDoInvariantsHold(const O1HeapInstance *const handle) O1HEAP_ASSERT(handle != NULL); bool valid = true; - invoke(handle, handle->critical_section_enter); + AU_LOCK_GUARD(handle->mutex); // Check the bin mask consistency. for (size_t i = 0; i < NUM_BINS_MAX; i++) // Dear compiler, feel free to unroll this loop. @@ -495,8 +480,6 @@ bool o1heapDoInvariantsHold(const O1HeapInstance *const handle) // Create a local copy of the diagnostics struct to check later and release the critical section early. const O1HeapDiagnostics diag = handle->diagnostics; - invoke(handle, handle->critical_section_leave); - // Capacity check. valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) && ((diag.capacity % FRAGMENT_SIZE_MIN) == 0U); @@ -524,8 +507,7 @@ bool o1heapDoInvariantsHold(const O1HeapInstance *const handle) O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance *const handle) { O1HEAP_ASSERT(handle != NULL); - invoke(handle, handle->critical_section_enter); + AU_LOCK_GUARD(handle->mutex); const O1HeapDiagnostics out = handle->diagnostics; - invoke(handle, handle->critical_section_leave); return out; } diff --git a/o1heap.hpp b/o1heap.hpp index d91616c..b6210e2 100644 --- a/o1heap.hpp +++ b/o1heap.hpp @@ -34,7 +34,6 @@ #include #include #include -#include /// The semantic version number of this distribution. #define O1HEAP_VERSION_MAJOR 1 @@ -51,9 +50,6 @@ /// The definition is private, so the user code can only operate on pointers. This is done to enforce encapsulation. typedef struct O1HeapInstance O1HeapInstance; -/// A hook function invoked by the allocator. NULL hooks are silently not invoked (not an error). -using O1HeapHook = std::function; - /// Runtime diagnostic information. This information can be used to facilitate runtime self-testing, /// as required by certain safety-critical development guidelines. /// If assertion checks are not disabled, the library will perform automatic runtime self-diagnostics that trigger @@ -109,9 +105,7 @@ typedef struct /// /// The time complexity is unspecified. O1HeapInstance *o1heapInit(void *const base, - const size_t size, - O1HeapHook critical_section_enter, - O1HeapHook critical_section_leave); + const size_t size); /// The semantics follows malloc() with additional guarantees the full list of which is provided below. /// @@ -138,8 +132,6 @@ void *o1heapAllocate(O1HeapInstance *const handle, const size_t amount); /// The function may invoke critical_section_enter and critical_section_leave at most once each (NULL hooks ignored). void o1heapFree(O1HeapInstance *const handle, void *const pointer); -void o1HeapReleaseCpp(O1HeapInstance *handle); - /// Performs a basic sanity check on the heap. /// This function can be used as a weak but fast method of heap corruption detection. /// It invokes critical_section_enter once (unless NULL) and then critical_section_leave once (unless NULL).