[*] Remove critical section APIs while keeping the original thread-safe implementation using rt futexes

[*] amend port: f30d5203
This commit is contained in:
Reece Wilson 2024-01-07 04:08:06 +00:00
parent f30d520343
commit 8f982a15d5
2 changed files with 29 additions and 55 deletions

View File

@ -115,8 +115,7 @@ struct O1HeapInstance
Fragment *bins[NUM_BINS_MAX]; ///< Smallest fragments are in the bin at index 0. 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. 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; AuFutexMutex mutex;
O1HeapHook critical_section_leave;
O1HeapDiagnostics diagnostics; O1HeapDiagnostics diagnostics;
}; };
@ -141,22 +140,23 @@ O1HEAP_PRIVATE uint8_t log2Floor(const size_t x)
O1HEAP_ASSERT(x > 0); O1HEAP_ASSERT(x > 0);
AuUInt8 index {}; AuUInt8 index {};
AuBitScanReverse(index, x); AuBitScanReverse(index, x);
return (uint8_t)(((sizeof(x) * CHAR_BIT) - 1U) - index); return index;
} }
O1HEAP_PRIVATE uint_fast8_t log2Ceil(const size_t x) O1HEAP_PRIVATE uint_fast8_t log2Ceil(const size_t x)
{ {
AuUInt8 index {}; AuUInt8 index {};
AuBitScanReverse(index, x - 1); AuBitScanReverse(index, x);
return (x <= 1U) ? 0U : (uint_fast8_t)((sizeof(x) * CHAR_BIT) - (index)); 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_PRIVATE size_t roundUpToPowerOf2(const size_t x)
{ {
O1HEAP_ASSERT(x >= 2U); return AuRoundUpPow2(x);
AuUInt8 index {};
AuBitScanReverse(index, x - 1);
return ((size_t)1U) << ((sizeof(x) * CHAR_BIT) - (index));
} }
/// Raise 2 into the specified power. /// Raise 2 into the specified power.
@ -168,15 +168,6 @@ O1HEAP_PRIVATE size_t pow2(const uint8_t power)
return ((size_t)1U) << 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. /// 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);
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 ---------------------------------------- // ---------------------------------------- PUBLIC API IMPLEMENTATION ----------------------------------------
O1HeapInstance *o1heapInit(void *const base, O1HeapInstance *o1heapInit(void *const base,
const size_t size, const size_t size)
O1HeapHook critical_section_enter,
O1HeapHook critical_section_leave)
{ {
O1HeapInstance *out = NULL; O1HeapInstance *out = NULL;
if ((base != NULL) && ((((size_t)base) % O1HEAP_ALIGNMENT) == 0U) && if ((base != NULL) && ((((size_t)base) % O1HEAP_ALIGNMENT) == 0U) &&
@ -262,8 +251,6 @@ O1HeapInstance *o1heapInit(void *const base,
out = (O1HeapInstance *)base; out = (O1HeapInstance *)base;
out->nonempty_bin_mask = 0U; 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++) for (size_t i = 0; i < NUM_BINS_MAX; i++)
{ {
out->bins[i] = NULL; 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); O1HEAP_ASSERT(optimal_bin_index < NUM_BINS_MAX);
const size_t candidate_bin_mask = ~(pow2(optimal_bin_index) - 1U); 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. // Find the smallest non-empty bin we can use.
const size_t suitable_bins = handle->nonempty_bin_mask & candidate_bin_mask; 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; out = ((uint8_t *)frag) + O1HEAP_ALIGNMENT;
} }
} }
else
{
invoke(handle, handle->critical_section_enter);
}
// Update the diagnostics. // 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; return out;
} }
void o1HeapReleaseCpp(O1HeapInstance *handle)
{
handle->~O1HeapInstance();
}
void o1heapFree(O1HeapInstance *const handle, void *const pointer) void o1heapFree(O1HeapInstance *const handle, void *const pointer)
{ {
O1HEAP_ASSERT(handle != NULL); 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 <= handle->diagnostics.capacity);
O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U); 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. // Even if we're going to drop the fragment later, mark it free anyway to prevent double-free.
frag->header.used = false; frag->header.used = false;
@ -472,8 +459,6 @@ void o1heapFree(O1HeapInstance *const handle, void *const pointer)
{ {
rebin(handle, frag); rebin(handle, frag);
} }
invoke(handle, handle->critical_section_leave);
} }
} }
@ -482,7 +467,7 @@ bool o1heapDoInvariantsHold(const O1HeapInstance *const handle)
O1HEAP_ASSERT(handle != NULL); O1HEAP_ASSERT(handle != NULL);
bool valid = true; bool valid = true;
invoke(handle, handle->critical_section_enter); AU_LOCK_GUARD(handle->mutex);
// Check the bin mask consistency. // Check the bin mask consistency.
for (size_t i = 0; i < NUM_BINS_MAX; i++) // Dear compiler, feel free to unroll this loop. 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. // Create a local copy of the diagnostics struct to check later and release the critical section early.
const O1HeapDiagnostics diag = handle->diagnostics; const O1HeapDiagnostics diag = handle->diagnostics;
invoke(handle, handle->critical_section_leave);
// Capacity check. // Capacity check.
valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) && valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) &&
((diag.capacity % FRAGMENT_SIZE_MIN) == 0U); ((diag.capacity % FRAGMENT_SIZE_MIN) == 0U);
@ -524,8 +507,7 @@ bool o1heapDoInvariantsHold(const O1HeapInstance *const handle)
O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance *const handle) O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance *const handle)
{ {
O1HEAP_ASSERT(handle != NULL); O1HEAP_ASSERT(handle != NULL);
invoke(handle, handle->critical_section_enter); AU_LOCK_GUARD(handle->mutex);
const O1HeapDiagnostics out = handle->diagnostics; const O1HeapDiagnostics out = handle->diagnostics;
invoke(handle, handle->critical_section_leave);
return out; return out;
} }

View File

@ -34,7 +34,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <functional>
/// The semantic version number of this distribution. /// The semantic version number of this distribution.
#define O1HEAP_VERSION_MAJOR 1 #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. /// The definition is private, so the user code can only operate on pointers. This is done to enforce encapsulation.
typedef struct O1HeapInstance O1HeapInstance; typedef struct O1HeapInstance O1HeapInstance;
/// A hook function invoked by the allocator. NULL hooks are silently not invoked (not an error).
using O1HeapHook = std::function<void(const O1HeapInstance *const handle)>;
/// Runtime diagnostic information. This information can be used to facilitate runtime self-testing, /// Runtime diagnostic information. This information can be used to facilitate runtime self-testing,
/// as required by certain safety-critical development guidelines. /// as required by certain safety-critical development guidelines.
/// If assertion checks are not disabled, the library will perform automatic runtime self-diagnostics that trigger /// 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. /// The time complexity is unspecified.
O1HeapInstance *o1heapInit(void *const base, O1HeapInstance *o1heapInit(void *const base,
const size_t size, const size_t size);
O1HeapHook critical_section_enter,
O1HeapHook critical_section_leave);
/// The semantics follows malloc() with additional guarantees the full list of which is provided below. /// 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). /// 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 o1heapFree(O1HeapInstance *const handle, void *const pointer);
void o1HeapReleaseCpp(O1HeapInstance *handle);
/// Performs a basic sanity check on the heap. /// Performs a basic sanity check on the heap.
/// This function can be used as a weak but fast method of heap corruption detection. /// 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). /// It invokes critical_section_enter once (unless NULL) and then critical_section_leave once (unless NULL).