Compare commits

...

2 commits

Author SHA1 Message Date
9223d06dbb
some tlsf helper functions and associated tests 2025-11-13 00:38:32 +01:00
f0d62111f3
tlsf pool initialization 2025-11-05 18:12:36 +01:00
5 changed files with 505 additions and 0 deletions

View file

@ -75,6 +75,12 @@ build/tests/test_mem_range: tests/test_mem_range.c $(TEST_MEM_RANGE_DEPS) build/
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS) @"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_MEM_RANGE_DEPS) @"$(CC)" -o $@ $@.o $(TEST_MEM_RANGE_DEPS)
TEST_TLSF_DEPS=build/src/test.o build/src/tlsf.o
build/tests/test_tlsf: tests/test_tlsf.c $(TEST_TLSF_DEPS) build/tests
@printf "CC %s\n" $@
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_TLSF_DEPS)
build/tests: build/tests:
mkdir -p $@ mkdir -p $@

View file

@ -14,5 +14,6 @@
__attribute__ ((noreturn)) __attribute__ ((noreturn))
void test_fail(const char *file, unsigned int line, const char *msg); void test_fail(const char *file, unsigned int line, const char *msg);
void test_success(const char *file, unsigned int line, const char *msg); void test_success(const char *file, unsigned int line, const char *msg);
void printf(const char *restrict fmt, ...);
#endif #endif

44
include/tlsf.h Normal file
View file

@ -0,0 +1,44 @@
#ifndef KARLOS_TLSF_H
#define KARLOS_TLSF_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct tlsf_pool {
// the total pool size, including space reserved for allocator structs.
size_t pool_size;
size_t sli_shift;
uint64_t bitmap;
struct tlsf_free_lv2 *free[];
};
struct tlsf_free_lv2 {
uint64_t bitmap;
struct tlsf_block_hdr *free[];
};
// #define TLSF_BLOCK_HDR_CANARY 0xb10c5555aaaab10c
struct tlsf_block_hdr {
// uint64_t canary;
uint64_t info;
struct tlsf_block_hdr *prev_phys_block;
struct tlsf_block_hdr *next_free;
struct tlsf_block_hdr *prev_free;
};
// TODO may inline this later, but leave it here now (also for testing)
void tlsf_mapping(size_t sz, size_t sli_shift, size_t *first, size_t *second);
size_t tlsf_num_lv1_buckets(size_t pool_size);
void tlsf_mapping_next(size_t sz, size_t sli_shift, size_t *first, size_t *second);
bool tlsf_find_free_list(struct tlsf_pool *pool, size_t sz, size_t *first_out, size_t *second_out);
void tlsf_mapping_range(size_t first, size_t second, size_t sli_shift, size_t *begin, size_t *end);
struct tlsf_pool *tlsf_init(void *mem, size_t pool_size, size_t sli_shift);
void *tlsf_malloc(struct tlsf_pool *pool, size_t size);
void tlsf_free(struct tlsf_pool *pool, void *addr);
#endif

216
src/tlsf.c Normal file
View file

@ -0,0 +1,216 @@
#include <stdint.h>
#include "std.h"
#include "tlsf.h"
// Highest bit set (0-indexed).
// - 4 (100) has highest bit 2.
// - 10 (1010) has highest bit 3.
// The following holds: (1 << HIGHEST_BIT_SET(sz)) <= sz
// Don't use for `sz == 0`.
#define HIGHEST_BIT_SET(sz) (63 - __builtin_clzl(sz))
// Calculate the mapping.
// `first` is highest bit set.
// `second` are the `sli_shift` bits below the highest bit set (padded with 0 to the right if necessary).
void tlsf_mapping(size_t sz, size_t sli_shift, size_t *first, size_t *second) {
ASSERT(sz > 0);
size_t hb = HIGHEST_BIT_SET(sz);
size_t mask = (1ull << sli_shift) - 1;
*first = hb;
if (hb >= sli_shift) {
*second = (sz >> (hb - sli_shift)) & mask;
} else {
*second = (sz << (sli_shift - hb)) & mask;
}
}
// index `i` in the level 1 lists is for [1 << (i+4), 1<<(i+5)]
// we don't need lists for elements < 16 because we round up anyways.
#define LV1_LIST_OFFSET 4
size_t tlsf_num_lv1_buckets(size_t pool_size) {
return HIGHEST_BIT_SET(pool_size) - LV1_LIST_OFFSET + 1;
}
#define IS_ALIGNED_16(sz) (((uint64_t)(sz) & (uint64_t)15) == 0)
#define ROUND_UP(n, shift) ((((uint64_t)n) + ((1ull << shift) - 1)) & ~(uint64_t)((1ull << shift) - 1))
// must be char pointer.
#define PTR_ADVANCE(ptr, base_struct, num_pointers) do { \
(ptr) += sizeof(base_struct) + num_pointers*sizeof(void *); \
(ptr) = (char *) ROUND_UP(ptr, 4); \
} while(0)
#define INFO_PACK(size, is_last_physical, is_free) \
(((uint64_t)(size) & ~3ull) | ((uint64_t)(is_last_physical) << 1) | ((uint64_t)(is_free)))
#define INFO_UNPACK_SIZE(info) ((info) & ~3ull)
#define INFO_UNPACK_IS_LAST_PHYSICAL(info) (((info) >> 1) & 0x1)
#define INFO_UNPACK_IS_FREE(info) ((info) & 0x1)
// Effective block size is the size that can actually be used by an allocation.
// subtract size of "used" header.
#define EFFECTIVE_BLOCK_SIZE(sz) ((sz) - (sizeof(struct tlsf_block_hdr) - 2*sizeof(struct tlsf_block_hdr *)))
struct tlsf_pool *tlsf_init(void *mem, size_t pool_size, size_t sli_shift) {
ASSERT(mem != NULL);
ASSERT(IS_ALIGNED_16(mem));
ASSERT(pool_size >= 4096); // TODO is this enough? Better estimate?
ASSERT(IS_ALIGNED_16(pool_size));
ASSERT(sli_shift <= 6);
struct tlsf_pool *lv1 = mem;
lv1->pool_size = pool_size;
lv1->sli_shift = sli_shift;
lv1->bitmap = 0;
size_t num_lv1_buckets = tlsf_num_lv1_buckets(pool_size);
char *currp = (char *) mem;
PTR_ADVANCE(currp, struct tlsf_pool, num_lv1_buckets);
for (size_t lv1_idx = 0; lv1_idx < num_lv1_buckets; lv1_idx++) {
// allocate second level
struct tlsf_free_lv2 *lv2 = (struct tlsf_free_lv2 *)currp;
lv1->free[lv1_idx] = lv2;
lv2->bitmap = 0;
memset(&lv2->free[0], 0, (1 << sli_shift) * sizeof(void *));
PTR_ADVANCE(currp, struct tlsf_free_lv2, (1 << sli_shift));
}
// free this one remaining block
struct tlsf_block_hdr *free_block = (struct tlsf_block_hdr *) currp;
size_t remaining = EFFECTIVE_BLOCK_SIZE((intptr_t)mem + pool_size - (intptr_t)currp);
ASSERT(remaining >= 16);
size_t first, second;
tlsf_mapping(remaining, sli_shift, &first, &second);
lv1->bitmap |= (1ull << (first - LV1_LIST_OFFSET));
struct tlsf_free_lv2 *lv2 = lv1->free[first - LV1_LIST_OFFSET];
lv2->bitmap |= (1ull << second);
lv2->free[second] = free_block;
free_block->info = INFO_PACK(remaining, true, true);
free_block->prev_phys_block = NULL;
free_block->next_free = free_block;
free_block->prev_free = free_block;
return lv1;
}
struct tlsf_free_lv2 *access_in_lv1(struct tlsf_pool *pool, size_t first) {
ASSERT(first >= LV1_LIST_OFFSET);
return pool->free[first - LV1_LIST_OFFSET];
}
// find smallest non-empty free list starting at index `first` (inclusive).
// This will be the smallest non-empty list including items >= `1 << first`.
struct tlsf_free_lv2 *find_in_lv1(struct tlsf_pool *pool, size_t first) {
ASSERT(first >= LV1_LIST_OFFSET);
uint64_t search = pool->bitmap >> (first - LV1_LIST_OFFSET);
if (search == 0) {
return NULL;
}
size_t move_up = __builtin_ffsl(search) - 1;
}
#define IS_POWER_OF_2(n) (__builtin_popcountl(n) == 1)
// returns if this `sz` is at the beginning (smallest size) of a bucket
bool tlsf_starts_range(size_t sz, size_t sli_shift) {
size_t hb = HIGHEST_BIT_SET(sz);
if (hb <= sli_shift) {
return true;
}
size_t mask = (1ull << (hb - sli_shift)) - 1;
return (sz & mask) == 0;
}
// returns the range that is described by first and second values, for testing
void tlsf_mapping_range(size_t first, size_t second, size_t sli_shift, size_t *begin, size_t *nelem) {
// TODO
size_t b = (1 << first);
if (first >= sli_shift) {
b |= second << (first - sli_shift);
} else {
b |= second >> (sli_shift - first);
}
*begin = b;
*nelem = (first > sli_shift) ? (1 << (first - sli_shift)) : 1;
}
// sets `first` and `second` so that they point to a range where all elements are >= sz
void tlsf_mapping_next(size_t sz, size_t sli_shift, size_t *first, size_t *second) {
tlsf_mapping(sz, sli_shift, first, second);
if (!tlsf_starts_range(sz, sli_shift)) {
if (*second + 1 < (1ull << sli_shift)) {
(*second)++;
} else {
(*first)++;
*second = 0;
}
}
}
#define LV1_BITMAP_GET(pool, first) (((pool)->bitmap >> (first - LV1_LIST_OFFSET)) & 1)
// modifies first
bool tlsf_lv1_bitmap_find(uint64_t bitmap, size_t *first) {
ASSERT(first != NULL && *first >= LV1_LIST_OFFSET);
bitmap >>= *first - LV1_LIST_OFFSET;
if (bitmap == 0) {
return false;
}
*first = __builtin_ctzl(bitmap) + *first;
return true;
}
bool tlsf_lv2_bitmap_find(uint64_t bitmap, size_t *second) {
ASSERT(second != NULL);
bitmap >>= *second;
if (bitmap == 0) {
return false;
}
*second = __builtin_ctzl(bitmap) + *second;
return true;
}
void *tlsf_malloc(struct tlsf_pool *pool, size_t sz) {
if (sz < 16) {
sz = 16;
}
size_t first, second;
tlsf_mapping_next(sz, pool->sli_shift, &first, &second);
size_t old_first = first;
if (!tlsf_lv1_bitmap_find(pool->bitmap, &first)) {
return NULL;
}
struct tlsf_free_lv2 *lv2 = pool->free[first - LV1_LIST_OFFSET];
if (old_first < first) {
second = 0;
}
if (!tlsf_lv2_bitmap_find(lv2->bitmap, &second)) {
return NULL;
}
// TODO
struct tlsf_block_hdr *hdr = lv2->free[second];
ASSERT(hdr != NULL);
ASSERT(INFO_UNPACK_SIZE(hdr->info) >= sz);
ASSERT(INFO_UNPACK_IS_FREE(hdr->info));
if (hdr->next_free == hdr) {
// list is now empty
lv2->free[second] = NULL;
lv2->bitmap &= ~(1ull << second);
} else {
lv2->free[second] = hdr->next_free;
// remove self from list
hdr->next_free->prev_free = hdr->prev_free;
hdr->prev_free->next_free = hdr->next_free;
}
hdr->info &= ~1ull; // clear 'free' bit
return &hdr->next_free; // this is the data that's not present in an occupied block
}
void tlsf_free(struct tlsf_pool *pool, void *addr) {
}
// TODO
// - circular linked list, is this correct?
// - what is T used for?

238
tests/test_tlsf.c Normal file
View file

@ -0,0 +1,238 @@
#include "tlsf.h"
#include "test.h"
void test_mapping(void) {
static size_t mapping_test[63][2] = {
{ 0, 0 },
{ 1, 0 },
{ 1, 8 },
{ 2, 0 },
{ 2, 4 },
{ 2, 8 },
{ 2, 12 },
{ 3, 0 },
{ 3, 2 },
{ 3, 4 },
{ 3, 6 },
{ 3, 8 },
{ 3, 10 },
{ 3, 12 },
{ 3, 14 },
{ 4, 0 },
{ 4, 1 },
{ 4, 2 },
{ 4, 3 },
{ 4, 4 },
{ 4, 5 },
{ 4, 6 },
{ 4, 7 },
{ 4, 8 },
{ 4, 9 },
{ 4, 10 },
{ 4, 11 },
{ 4, 12 },
{ 4, 13 },
{ 4, 14 },
{ 4, 15 },
{ 5, 0 },
{ 5, 0 },
{ 5, 1 },
{ 5, 1 },
{ 5, 2 },
{ 5, 2 },
{ 5, 3 },
{ 5, 3 },
{ 5, 4 },
{ 5, 4 },
{ 5, 5 },
{ 5, 5 },
{ 5, 6 },
{ 5, 6 },
{ 5, 7 },
{ 5, 7 },
{ 5, 8 },
{ 5, 8 },
{ 5, 9 },
{ 5, 9 },
{ 5, 10 },
{ 5, 10 },
{ 5, 11 },
{ 5, 11 },
{ 5, 12 },
{ 5, 12 },
{ 5, 13 },
{ 5, 13 },
{ 5, 14 },
{ 5, 14 },
{ 5, 15 },
{ 5, 15 }
};
size_t first, second;
for (size_t i = 0; i < 63; i++) {
tlsf_mapping(i + 1, 4, &first, &second);
TEST_ASSERT(first == mapping_test[i][0] && second == mapping_test[i][1]);
}
}
void test_bucket_size(void) {
for (size_t i = 16; i < 32; i++) {
TEST_ASSERT(tlsf_num_lv1_buckets(i) == 1);
}
TEST_ASSERT(tlsf_num_lv1_buckets(32) == 2);
TEST_ASSERT(tlsf_num_lv1_buckets(63) == 2);
TEST_ASSERT(tlsf_num_lv1_buckets(64) == 3); // 2^6, 2^5, 2^4
}
void test_access(struct tlsf_pool *pool) {
pool->bitmap = 1ull << 2;
}
void test_mapping_range(void) {
size_t begin, nelem;
tlsf_mapping_range(1, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 2 && nelem == 1);
tlsf_mapping_range(1, 8, 4, &begin, &nelem);
TEST_ASSERT(begin == 3 && nelem == 1);
tlsf_mapping_range(2, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 4 && nelem == 1);
tlsf_mapping_range(2, 4, 4, &begin, &nelem);
TEST_ASSERT(begin == 5 && nelem == 1);
tlsf_mapping_range(2, 8, 4, &begin, &nelem);
TEST_ASSERT(begin == 6 && nelem == 1);
tlsf_mapping_range(2, 12, 4, &begin, &nelem);
TEST_ASSERT(begin == 7 && nelem == 1);
tlsf_mapping_range(3, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 8 && nelem == 1);
tlsf_mapping_range(3, 2, 4, &begin, &nelem);
TEST_ASSERT(begin == 9 && nelem == 1);
tlsf_mapping_range(3, 4, 4, &begin, &nelem);
TEST_ASSERT(begin == 10 && nelem == 1);
tlsf_mapping_range(3, 6, 4, &begin, &nelem);
TEST_ASSERT(begin == 11 && nelem == 1);
tlsf_mapping_range(3, 8, 4, &begin, &nelem);
TEST_ASSERT(begin == 12 && nelem == 1);
tlsf_mapping_range(3, 10, 4, &begin, &nelem);
TEST_ASSERT(begin == 13 && nelem == 1);
tlsf_mapping_range(3, 12, 4, &begin, &nelem);
TEST_ASSERT(begin == 14 && nelem == 1);
tlsf_mapping_range(3, 14, 4, &begin, &nelem);
TEST_ASSERT(begin == 15 && nelem == 1);
tlsf_mapping_range(4, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 16 && nelem == 1);
tlsf_mapping_range(4, 1, 4, &begin, &nelem);
TEST_ASSERT(begin == 17 && nelem == 1);
tlsf_mapping_range(4, 2, 4, &begin, &nelem);
TEST_ASSERT(begin == 18 && nelem == 1);
// ...
tlsf_mapping_range(4, 15, 4, &begin, &nelem);
TEST_ASSERT(begin == 31 && nelem == 1);
tlsf_mapping_range(5, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 32 && nelem == 2);
tlsf_mapping_range(5, 1, 4, &begin, &nelem);
TEST_ASSERT(begin == 34 && nelem == 2);
tlsf_mapping_range(6, 0, 4, &begin, &nelem);
TEST_ASSERT(begin == 64 && nelem == 4);
tlsf_mapping_range(6, 1, 4, &begin, &nelem);
TEST_ASSERT(begin == 68 && nelem == 4);
}
void test_mapping_next(void) {
size_t first, second;
size_t begin, nelem;
// try for each size
for (size_t sz = 1; sz < 256; sz++) {
tlsf_mapping_next(sz, 4, &first, &second);
tlsf_mapping_range(first, second, 4, &begin, &nelem);
TEST_ASSERT(sz <= begin);
// now, test that we actually got the smallest range
if (second > 0) {
second--;
} else if (first > 1) {
first--;
} else {
continue;
}
tlsf_mapping_range(first, second, 4, &begin, &nelem);
TEST_ASSERT(sz > begin);
}
}
bool tlsf_starts_range(size_t, size_t);
void test_starts_range() {
for (size_t i = 1; i < 32; i++) {
TEST_ASSERT(tlsf_starts_range(i, 4));
}
for (size_t i = 32; i < 64; i++) {
TEST_ASSERT(tlsf_starts_range(i, 4) == (i%2 == 0));
}
for (size_t i = 64; i < 128; i++) {
TEST_ASSERT(tlsf_starts_range(i, 4) == (i%4 == 0));
}
}
bool tlsf_lv1_bitmap_find(uint64_t, size_t *);
void test_lv1_bitmap_find() {
size_t first;
first = 4;
TEST_ASSERT(!tlsf_lv1_bitmap_find(0x00, &first));
first = 4;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x01, &first) && first == 4);
first = 4;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x02, &first) && first == 5);
first = 4;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x03, &first) && first == 4);
first = 4;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x04, &first) && first == 6);
first = 4;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x05, &first) && first == 4);
first = 5;
TEST_ASSERT(!tlsf_lv1_bitmap_find(0x00, &first));
first = 5;
TEST_ASSERT(!tlsf_lv1_bitmap_find(0x01, &first));
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x02, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x03, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x04, &first) && first == 6);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x05, &first) && first == 6);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x06, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x07, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x08, &first) && first == 7);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x09, &first) && first == 7);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x0a, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x0b, &first) && first == 5);
first = 5;
TEST_ASSERT(tlsf_lv1_bitmap_find(0x0c, &first) && first == 6);
}
#define POOL_SIZE 4096
char mem[POOL_SIZE] __attribute__ ((aligned (16)));
void main() {
test_mapping();
TEST_ASSERT(1 == 1);
test_bucket_size();
TEST_ASSERT(2 == 2);
test_starts_range();
test_mapping_range();
test_mapping_next();
test_lv1_bitmap_find();
for (int i = 0; i < POOL_SIZE; i++) ((char*)mem)[i] = 0x55;
tlsf_init(mem, POOL_SIZE, 4);
}