Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions include/tlsf.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ size_t tlsf_append_pool(tlsf_t *tlsf, void *mem, size_t size);
*/
size_t tlsf_pool_init(tlsf_t *t, void *mem, size_t bytes);

/**
* Reset a static pool to its initial state, discarding all allocations.
* Bounded-time bulk deallocation: clears bitmaps, recreates a single
* free block. Cost is O(FL_COUNT * SL_COUNT) for the bin reset, which
* is fixed at compile time.
*
* Only valid for pools created with tlsf_pool_init().
* Does nothing for dynamic pools or uninitialized instances.
*
* WARNING: All pointers previously returned by tlsf_malloc/aalloc/realloc
* become invalid after reset. Passing stale pointers to tlsf_free or
* tlsf_realloc causes undefined behavior (silent metadata corruption in
* release builds, assertion failure in debug builds).
*
* @param t The TLSF allocator instance
*/
void tlsf_pool_reset(tlsf_t *t);

/**
* Allocate memory from the pool.
*
Expand Down
35 changes: 35 additions & 0 deletions src/tlsf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ size_t tlsf_pool_init(tlsf_t *t, void *mem, size_t bytes)
/* Set up sentinel at the end of the free block */
tlsf_block_t *sentinel = block_link_next(block);
sentinel->header = BLOCK_BIT_PREV_FREE;
check_sentinel(sentinel);

t->size = free_size + 2 * BLOCK_OVERHEAD;

Expand All @@ -1031,6 +1032,40 @@ size_t tlsf_pool_init(tlsf_t *t, void *mem, size_t bytes)
return free_size;
}

void tlsf_pool_reset(tlsf_t *t)
{
if (!t || !t->arena)
return;

/* Unpoison the entire pool for ASan. */
ASAN_UNPOISON(t->arena, t->size);

/* Clear bitmaps. */
t->fl = 0;
memset(t->sl, 0, sizeof(t->sl));

/* Reset all bin pointers to sentinel. */
for (uint32_t i = 0; i < FL_COUNT; i++)
for (uint32_t j = 0; j < SL_COUNT; j++)
t->block[i][j] = &t->block_null;

/* Reconstruct the single free block spanning the entire pool.
* Same layout as the second half of tlsf_pool_init().
*/
size_t free_size = t->size - 2 * BLOCK_OVERHEAD;

tlsf_block_t *block = to_block((char *) t->arena - BLOCK_OVERHEAD);
block->header = free_size | BLOCK_BIT_FREE;
block_insert(t, block);

/* Sentinel at the end of the pool. */
tlsf_block_t *sentinel = block_link_next(block);
sentinel->header = BLOCK_BIT_PREV_FREE;
check_sentinel(sentinel);

block_poison_free(block);
}

#ifdef TLSF_ENABLE_CHECK
#include <stdio.h>
#include <stdlib.h>
Expand Down
123 changes: 123 additions & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,126 @@ static void zero_size_align_test(tlsf_t *t)
printf(". done\n");
}

/* Test pool reset: O(1) bulk deallocation for static pools. */
static void pool_reset_test(void)
{
printf("Pool reset test: ");
fflush(stdout);

/* Test 1: Basic reset - allocate, reset, verify clean state */
{
static char pool[1024 * 64]; /* 64 KB */
tlsf_t t;
size_t usable = tlsf_pool_init(&t, pool, sizeof(pool));
assert(usable > 0);

/* Fill pool with allocations */
void *ptrs[100];
int count = 0;
for (int i = 0; i < 100; i++) {
ptrs[i] = tlsf_malloc(&t, 200);
if (!ptrs[i])
break;
memset(ptrs[i], 0xAB, 200);
count++;
}
assert(count > 0);

/* Reset discards all allocations */
tlsf_pool_reset(&t);
tlsf_check(&t);

/* Pool should be back to initial state: one free block */
tlsf_stats_t stats;
int rc = tlsf_get_stats(&t, &stats);
assert(rc == 0);
assert(stats.free_count == 1);
assert(stats.total_used == 0);
assert(stats.total_free == usable);

/* Pool should be usable after reset */
void *p = tlsf_malloc(&t, 100);
assert(p);
tlsf_free(&t, p);
tlsf_check(&t);
}
printf(".");
fflush(stdout);

/* Test 2: Multiple resets in a loop */
{
static char pool[16384];
tlsf_t t;
size_t usable = tlsf_pool_init(&t, pool, sizeof(pool));
assert(usable > 0);

for (int round = 0; round < 10; round++) {
void *p = tlsf_malloc(&t, 100 * ((size_t) round + 1));
assert(p);
void *q = tlsf_malloc(&t, 50);
assert(q);
tlsf_free(&t, p);
/* q is intentionally leaked - reset discards it */

tlsf_pool_reset(&t);
tlsf_check(&t);

/* Verify clean state each round */
tlsf_stats_t stats;
tlsf_get_stats(&t, &stats);
assert(stats.free_count == 1);
assert(stats.total_used == 0);
assert(stats.total_free == usable);
}
}
printf(".");
fflush(stdout);

/* Test 3: Reset after append_pool preserves expanded capacity */
{
static char combined[8192];
tlsf_t t;
size_t half = 4096;
size_t usable = tlsf_pool_init(&t, combined, half);
assert(usable > 0);

/* Append second half (adjacent by construction) */
size_t appended = tlsf_append_pool(&t, combined + half, half);
assert(appended > 0);

/* Record full capacity before allocations */
tlsf_stats_t full_stats;
tlsf_get_stats(&t, &full_stats);
size_t full_free = full_stats.total_free;
assert(full_free > usable); /* Expanded pool is larger */

/* Allocate and fragment */
void *p1 = tlsf_malloc(&t, 100);
assert(p1);

/* Reset should restore full expanded capacity */
tlsf_pool_reset(&t);
tlsf_check(&t);

tlsf_stats_t after;
tlsf_get_stats(&t, &after);
assert(after.free_count == 1);
assert(after.total_used == 0);
assert(after.total_free == full_free);
}
printf(".");
fflush(stdout);

/* Test 4: Reset on NULL or dynamic pool is a no-op */
{
tlsf_pool_reset(NULL);

tlsf_t t = TLSF_INIT;
tlsf_pool_reset(&t); /* arena is NULL, no-op */
}
printf(". done\n");
}

int main(void)
{
PAGE = (size_t) sysconf(_SC_PAGESIZE);
Expand Down Expand Up @@ -810,6 +930,9 @@ int main(void)
/* Run static pool test */
static_pool_test();

/* Run pool reset test */
pool_reset_test();

puts("OK!");
return 0;
}