From 3bfefccdcd054e7286f3e15092bfd8a6ce15e3bb Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 10 Feb 2026 01:28:06 +0800 Subject: [PATCH] Add bounded-time pool reset for static pools Arena-style workloads (allocate many, discard all) currently require per-object tlsf_free() calls. tlsf_pool_reset() clears the FL/SL bitmaps, resets bin pointers to the sentinel, and reconstructs a single free block plus sentinel -- the same layout tlsf_pool_init() produces. Pools extended via tlsf_append_pool() retain their full expanded capacity across resets. This also adds check_sentinel() after sentinel construction in tlsf_pool_init() for debug-mode invariant enforcement. --- include/tlsf.h | 18 ++++++++ src/tlsf.c | 35 ++++++++++++++ tests/test.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) diff --git a/include/tlsf.h b/include/tlsf.h index c209ea3..ef18ef3 100644 --- a/include/tlsf.h +++ b/include/tlsf.h @@ -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. * diff --git a/src/tlsf.c b/src/tlsf.c index 40d4298..998cfa6 100644 --- a/src/tlsf.c +++ b/src/tlsf.c @@ -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; @@ -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 #include diff --git a/tests/test.c b/tests/test.c index c051c00..be9f64e 100644 --- a/tests/test.c +++ b/tests/test.c @@ -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); @@ -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; }