/* * M*LIB - MEMPOOL module * * Copyright (c) 2017-2023, Patrick Pelissier * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * + Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MSTARLIB_MEMPOOL_H #define MSTARLIB_MEMPOOL_H #include "m-core.h" /* Fast, fixed size, thread unsafe allocator based on memory regions. No oplist is needed. USAGE: MEMPOOL_DEF(name, type) Example: MEMPOOL_DEF(mempool_uint, unsigned int) ... mempool_uint_t m; mempool_uint_init(m); unsigned int *ptr = mempool_uint_alloc(m); *ptr = 17; mempool_uint_free(m, ptr); mempool_uint_clear(m); // Give back memory to system */ #define M_MEMPOOL_DEF(name, type) \ M_MEMPOOL_DEF_AS(name, M_F(name,_t), type) /* Fast, fixed Size, thread unsafe allocator based on memory region. USAGE: MEMPOOL_DEF_AS(name, name_t, type) */ #define M_MEMPOOL_DEF_AS(name, name_t, type) \ M_BEGIN_PROTECTED_CODE \ M_M3MPOOL_DEF_P2(name, type, name_t ) \ M_END_PROTECTED_CODE /* User shall be able to cutomize the size of the region segment and/or the minimun number of elements. The default is the number of elements that fits in 16KB, or 256 is the size of the type is too big. */ #ifndef M_USE_MEMPOOL_MAX_PER_SEGMENT #define M_USE_MEMPOOL_MAX_PER_SEGMENT(type) \ M_MAX((16*1024-sizeof(unsigned int) - 2*sizeof(void*)) / sizeof (type), 256U) #endif /*****************************************************************************/ /********************************** INTERNAL *********************************/ /*****************************************************************************/ /* Technically, it uses a list of memory regions, where multiple allocations are performed in each region. However, it can not use m-list since it may be expanded from LIST_DEF (recursive dependency problem). */ #define M_M3MPOOL_DEF_P2(name, type, name_t) \ M_M3MPOOL_DEF_TYPE(name, type, name_t) \ M_M3MPOOL_DEF_CORE(name, type, name_t) /* Define the types of the mempool */ #define M_M3MPOOL_DEF_TYPE(name, type, name_t) \ \ /* Define the type of element in a segment of the mempool. \ Either it is the basic type or a pointer to another one. */ \ typedef union M_F(name,_union_s) { \ type t; \ union M_F(name,_union_s) *next; \ } M_F(name,_union_ct); \ \ /* Define a segment of a mempool. \ It is an array of basic type, each segment is in a linked list */ \ typedef struct M_F(name,_segment_s) { \ unsigned int count; \ struct M_F(name,_segment_s) *next; \ M_F(name,_union_ct) tab[M_USE_MEMPOOL_MAX_PER_SEGMENT(type)]; \ } M_F(name,_segment_ct); \ \ /* Define a mempool. \ It is a pointer to the first free object within the segments \ and the segments themselves */ \ typedef struct M_F(name, _s) { \ M_F(name,_union_ct) *free_list; \ M_F(name,_segment_ct) *current_segment; \ } name_t[1]; \ /* Define the core functions of the mempool */ #define M_M3MPOOL_DEF_CORE(name, type, name_t) \ \ M_INLINE void \ M_F(name,_init)(name_t mem) \ { \ mem->free_list = NULL; \ mem->current_segment = M_MEMORY_ALLOC(M_F(name,_segment_ct)); \ if (M_UNLIKELY_NOMEM(mem->current_segment == NULL)) { \ M_MEMORY_FULL(sizeof (M_F(name,_segment_ct))); \ return; \ } \ mem->current_segment->next = NULL; \ mem->current_segment->count = 0; \ M_M3MPOOL_CONTRACT(mem, type); \ } \ \ M_INLINE void \ M_F(name,_clear)(name_t mem) \ { \ M_M3MPOOL_CONTRACT(mem, type); \ M_F(name,_segment_ct) *segment = mem->current_segment; \ while (segment != NULL) { \ M_F(name,_segment_ct) *next = segment->next; \ M_MEMORY_DEL (segment); \ segment = next; \ } \ /* Clean pointers to be safer */ \ mem->free_list = NULL; \ mem->current_segment = NULL; \ } \ \ M_INLINE type * \ M_F(name,_alloc)(name_t mem) \ { \ M_M3MPOOL_CONTRACT(mem, type); \ /* Test if one object is in the free list */ \ M_F(name,_union_ct) *ret = mem->free_list; \ if (ret != NULL) { \ /* Yes, so return it, and pop it from the free list */ \ mem->free_list = ret->next; \ return &ret->t; \ } \ /* No cheap free object exist. Test within a segment */ \ M_F(name,_segment_ct) *segment = mem->current_segment; \ M_ASSERT(segment != NULL); \ unsigned int count = segment->count; \ /* If segment is full, allocate a new one from the system */ \ if (M_UNLIKELY (count >= M_USE_MEMPOOL_MAX_PER_SEGMENT(type))) { \ M_F(name,_segment_ct) *new_segment = M_MEMORY_ALLOC (M_F(name,_segment_ct)); \ if (M_UNLIKELY_NOMEM (new_segment == NULL)) { \ M_MEMORY_FULL(sizeof (M_F(name,_segment_ct))); \ return NULL; \ } \ new_segment->next = segment; \ new_segment->count = 0; \ mem->current_segment = new_segment; \ segment = new_segment; \ count = 0; \ } \ /* Return the object as the last element of the current segment */ \ ret = &segment->tab[count]; \ segment->count = count + 1; \ M_M3MPOOL_CONTRACT(mem, type); \ return &ret->t; \ } \ \ M_INLINE void \ M_F(name,_free)(name_t mem, type *ptr) \ { \ M_M3MPOOL_CONTRACT(mem, type); \ /* NOTE: Unsafe cast: suppose that the given pointer \ was allocated by the previous alloc function. */ \ M_F(name,_union_ct) *ret = (M_F(name,_union_ct) *)(uintptr_t)ptr; \ /* Add the object back in the free list */ \ ret->next = mem->free_list; \ mem->free_list = ret; \ /* NOTE: the objects are NOT given back to the system until the mempool \ is fully cleared */ \ M_M3MPOOL_CONTRACT(mem, type); \ } \ /* MEMPOOL contract. We only control the current segment. */ #define M_M3MPOOL_CONTRACT(mempool, type) do { \ M_ASSERT((mempool) != NULL); \ M_ASSERT((mempool)->current_segment != NULL); \ M_ASSERT((mempool)->current_segment->count <= M_USE_MEMPOOL_MAX_PER_SEGMENT(type)); \ } while (0) /********************************** INTERNAL *********************************/ #if M_USE_SMALL_NAME #define MEMPOOL_DEF M_MEMPOOL_DEF #define MEMPOOL_DEF_AS M_MEMPOOL_DEF_AS #endif #endif