Skip to content

Commit

Permalink
Remove critical section hooks; fix #4
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-kirienko committed Oct 3, 2021
1 parent d173372 commit 85a31fb
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 178 deletions.
42 changes: 4 additions & 38 deletions o1heap/o1heap.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,6 @@ 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;

O1HeapDiagnostics diagnostics;
};

Expand Down Expand Up @@ -141,7 +138,7 @@ O1HEAP_PRIVATE uint8_t log2Floor(const size_t x)
O1HEAP_PRIVATE uint8_t log2Ceil(const size_t x);
O1HEAP_PRIVATE uint8_t log2Ceil(const size_t x)
{
return (uint8_t)(log2Floor(x) + (isPowerOf2(x) ? 0U : 1U));
return (uint8_t) (log2Floor(x) + (isPowerOf2(x) ? 0U : 1U));
}

/// Raise 2 into the specified power.
Expand All @@ -153,15 +150,6 @@ O1HEAP_PRIVATE size_t pow2(const uint8_t power)
return ((size_t) 1U) << power;
}

O1HEAP_PRIVATE void invoke(const O1HeapHook hook);
O1HEAP_PRIVATE void invoke(const O1HeapHook hook)
{
if (hook != NULL)
{
hook();
}
}

/// 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)
Expand Down Expand Up @@ -231,21 +219,16 @@ O1HEAP_PRIVATE void unbin(O1HeapInstance* const handle, const Fragment* const fr

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

O1HeapInstance* o1heapInit(void* const base,
const size_t size,
const O1HeapHook critical_section_enter,
const O1HeapHook critical_section_leave)
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)))
{
// Allocate the core heap metadata structure in the beginning of the arena.
O1HEAP_ASSERT(((size_t) base) % sizeof(O1HeapInstance*) == 0U);
out = (O1HeapInstance*) base;
out->nonempty_bin_mask = 0U;
out->critical_section_enter = critical_section_enter;
out->critical_section_leave = critical_section_leave;
out = (O1HeapInstance*) base;
out->nonempty_bin_mask = 0U;
for (size_t i = 0; i < NUM_BINS_MAX; i++)
{
out->bins[i] = NULL;
Expand Down Expand Up @@ -311,8 +294,6 @@ 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->critical_section_enter);

// Find the smallest non-empty bin we can use.
const size_t suitable_bins = handle->nonempty_bin_mask & candidate_bin_mask;
const size_t smallest_bin_mask = suitable_bins & ~(suitable_bins - 1U); // Clear all bits but the lowest.
Expand Down Expand Up @@ -363,10 +344,6 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
out = ((uint8_t*) frag) + O1HEAP_ALIGNMENT;
}
}
else
{
invoke(handle->critical_section_enter);
}

// Update the diagnostics.
if (O1HEAP_LIKELY(handle->diagnostics.peak_request_size < amount))
Expand All @@ -378,7 +355,6 @@ void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)
handle->diagnostics.oom_count++;
}

invoke(handle->critical_section_leave);
return out;
}

Expand All @@ -402,8 +378,6 @@ 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->critical_section_enter);

// Even if we're going to drop the fragment later, mark it free anyway to prevent double-free.
frag->header.used = false;

Expand Down Expand Up @@ -449,8 +423,6 @@ void o1heapFree(O1HeapInstance* const handle, void* const pointer)
{
rebin(handle, frag);
}

invoke(handle->critical_section_leave);
}
}

Expand All @@ -459,8 +431,6 @@ bool o1heapDoInvariantsHold(const O1HeapInstance* const handle)
O1HEAP_ASSERT(handle != NULL);
bool valid = true;

invoke(handle->critical_section_enter);

// Check the bin mask consistency.
for (size_t i = 0; i < NUM_BINS_MAX; i++) // Dear compiler, feel free to unroll this loop.
{
Expand All @@ -472,8 +442,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->critical_section_leave);

// Capacity check.
valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) &&
((diag.capacity % FRAGMENT_SIZE_MIN) == 0U);
Expand Down Expand Up @@ -501,8 +469,6 @@ bool o1heapDoInvariantsHold(const O1HeapInstance* const handle)
O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle)
{
O1HEAP_ASSERT(handle != NULL);
invoke(handle->critical_section_enter);
const O1HeapDiagnostics out = handle->diagnostics;
invoke(handle->critical_section_leave);
return out;
}
31 changes: 5 additions & 26 deletions o1heap/o1heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ extern "C" {
/// 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).
typedef void (*O1HeapHook)(void);

/// 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
Expand Down Expand Up @@ -75,14 +72,6 @@ typedef struct
/// the excess will be silently truncated away (no error). This is not a realistic use case because a typical
/// application is unlikely to be able to dedicate that much of the address space for the heap.
///
/// The critical section enter/leave callbacks will be invoked when the allocator performs an atomic transaction.
/// There is at most one atomic transaction per allocation/deallocation.
/// Either or both of the callbacks may be NULL if locking is not needed (i.e., the heap is not shared).
/// It is guaranteed that a critical section will never be entered recursively.
/// It is guaranteed that 'enter' is invoked the same number of times as 'leave', unless either of them are NULL.
/// It is guaranteed that 'enter' is invoked before 'leave', unless either of them are NULL.
/// The callbacks are never invoked from the initialization function itself.
///
/// The function initializes a new heap instance allocated in the provided arena, taking some of its space for its
/// 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.
Expand All @@ -92,11 +81,8 @@ typedef struct
/// 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.
///
/// The time complexity is unspecified.
O1HeapInstance* o1heapInit(void* const base,
const size_t size,
const O1HeapHook critical_section_enter,
const O1HeapHook critical_section_leave);
/// The heap is not thread-safe; external synchronization may be required.
O1HeapInstance* o1heapInit(void* const base, const size_t size);

/// The semantics follows malloc() with additional guarantees the full list of which is provided below.
///
Expand All @@ -106,34 +92,27 @@ O1HeapInstance* o1heapInit(void* const base,
/// If the allocation request cannot be served due to the lack of memory or its excessive fragmentation,
/// a NULL pointer is returned.
///
/// The function is executed in constant time (unless the critical section management hooks are used and are not
/// constant-time). The allocated memory is NOT zero-filled (because zero-filling is a variable-complexity operation).
///
/// The function may invoke critical_section_enter and critical_section_leave at most once each (NULL hooks ignored).
/// The function is executed in constant time.
/// The allocated memory is NOT zero-filled (because zero-filling is a variable-complexity operation).
void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount);

/// The semantics follows free() with additional guarantees the full list of which is provided below.
///
/// If the pointer does not point to a previously allocated block and is not NULL, the behavior is undefined.
/// Builds where assertion checks are enabled may trigger an assertion failure for some invalid inputs.
///
/// The function is executed in constant time (unless the critical section management hooks are used and are not
/// constant-time).
///
/// The function may invoke critical_section_enter and critical_section_leave at most once each (NULL hooks ignored).
/// The function is executed in constant time.
void o1heapFree(O1HeapInstance* const handle, void* const pointer);

/// 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).
/// If the handle pointer is NULL, the behavior is undefined.
/// The time complexity is constant.
/// The return value is truth if the heap looks valid, falsity otherwise.
bool o1heapDoInvariantsHold(const O1HeapInstance* const handle);

/// Samples and returns a copy of the diagnostic information, see @ref O1HeapDiagnostics.
/// This function merely copies the structure from an internal storage, so it is fast to return.
/// It invokes critical_section_enter once (unless NULL) and then critical_section_leave once (unless NULL).
/// If the handle pointer is NULL, the behavior is undefined.
O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle);

Expand Down
4 changes: 0 additions & 4 deletions tests/internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ auto isPowerOf2(const std::size_t x) -> bool;
auto log2Floor(const std::size_t x) -> std::uint8_t;
auto log2Ceil(const std::size_t x) -> std::uint8_t;
auto pow2(const std::uint8_t power) -> std::size_t;
void invoke(const O1HeapHook hook);
}

struct Fragment;
Expand Down Expand Up @@ -146,9 +145,6 @@ struct O1HeapInstance final

std::size_t nonempty_bin_mask = 0;

O1HeapHook critical_section_enter = nullptr;
O1HeapHook critical_section_leave = nullptr;

/// The same data is available via getDiagnostics(). The duplication is intentional.
O1HeapDiagnostics diagnostics{};

Expand Down
Loading

0 comments on commit 85a31fb

Please sign in to comment.