/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "config.h" /**@defgroup su_alloc Memory Management Tutorial * * This page gives a short overview of home-based memory management used * with Sofia. Such home-based memory management is useful when a lot of * memory blocks are allocated for given task. The allocations are done via * the @e memory @e home, which keeps a reference to each block. When the * memory home is then freed, it will free all blocks to which it has * reference. * * Typically, there is a @e home @e object which contains a su_home_t * structure in the beginning of the object (sort of inheritance from * su_home_t): * @code * struct context { * su_home_t ctx_home[1]; * other_t *ctx_stuff; * ... * } * @endcode * * A new home memory pool can be created with su_home_new(): * @code * struct context *ctx = su_home_new(sizeof (struct context)); * @endcode * * It is also possible to create a secondary memory pool that can be * released separately: * * @code * struct context *ctx = su_home_clone(tophome, sizeof (struct context)); * @endcode * * Note that the tophome has a reference to @a ctx structure; whenever * tophome is freed, the @a ctx is also freed. * * You can also create an independent home object by passing NULL as @a * tophome argument. This is identical to the call to su_home_new(). * * The memory allocations using @a ctx proceed then as follows: * @code * zeroblock = su_zalloc(ctx->ctx_home, sizeof (*zeroblock)); * @endcode * * The home memory pool - the home object and all the memory blocks * allocated using it - are freed when su_home_unref() is called: * * @code * su_home_unref(ctx->ctx_home). * @endcode * * @note For historical reasons, su_home_unref() is also known as * su_home_zap(). * * As you might have guessed, it is also possible to use reference counting * with home objects. The function su_home_ref() increases the reference * count, su_home_unref() decreases it. A newly allocated or initialized * home object has reference count of 1. * * @note Please note that while it is possible to create new references to * secondary home objects which have a parent home, the secondary home * objects will always be destroyed when the parent home is destroyed even * if there are other references left to them. * * The memory blocks in a cloned home object are freed when the object with * home itself is freed: * @code * su_free(tophome, ctx); * @endcode * * @note * * The su_home_destroy() function is deprecated as it does not free the home * object itself. Like su_home_deinit(), it should be called only on home * objects with reference count of 1. * * The function su_home_init() initializes a home object structure. When the * initialized home object is destroyed or deinitialized or its reference * count reaches zero, the memory allocate thorugh it reclaimed but the home * object structure itself is not freed. * * @section su_home_destructor_usage Destructors * * It is possible to give a destructor function to a home object. The * destructor releases other resources associated with the home object * besides memory. The destructor function will be called when the reference * count of home reaches zero (upon calling su_home_unref()) or the home * object is otherwise deinitialized (calling su_home_deinit() on * objects allocated from stack). * * @section su_home_move_example Combining Allocations * * In some cases, an operation that makes multiple memory allocations may * fail, making those allocations redundant. If the allocations are made * through a temporary home, they can be conveniently freed by calling * su_home_deinit(), for instance. If, however, the operation is successful, * and one wants to keep the allocations, the allocations can be combined * into an existing home with su_home_move(). For example, * @code * int example(su_home_t *home, ...) * { * su_home_t temphome[1] = { SU_HOME_INIT(temphome) }; * * ... do lot of allocations with temphome ... * * if (success) * su_home_move(home, temphome); * su_home_deinit(temphome); * * return success; * } * @endcode * * Note that the @a temphome is deinitialized in every case, but when * operation is successful, the allocations are moved from @a temphome to @a * home. * * @section su_alloc_threadsafe Threadsafe Operation * * If multiple threads need to access same home object, it must be marked as * @e threadsafe by calling su_home_threadsafe() with the home pointer as * argument. The threadsafeness is not inherited by clones. * * The threadsafe home objects can be locked and unlocked with * su_home_mutex_lock() and su_home_mutex_unlock(). These operations are * no-op on home object that is not threadsafe. * * @section su_alloc_preloading Preloading a Memory Home * * In some situations there is quite heavy overhead if the global heap * allocator is used. The overhead caused by the large number of small * allocations can be reduced by using su_home_preload(): it allocates or * preloads some a memory to home to be used as a kind of private heap. The * preloaded memory area is then used to satisfy small enough allocations. * For instance, the SIP parser typically preloads some 2K of memory when it * starts to parse the message. * * @section su_alloc_stack Using Stack * * In some situation, it is sensible to use memory allocated from stack for * some operations. The su_home_auto() function can be used for that * purpose. The memory area from stack is used to satisfy the allocations as * far as possible; if it is not enough, allocation is made from heap. * * The word @e auto refers to the automatic scope; however, the home object * that was initialized with su_home_auto() must be explicitly deinitialized * with su_home_deinit() or su_home_unref() when the program exits the scope * where the stack frame used in su_home_auto() was allocated. */ /**@ingroup su_alloc * @CFILE su_alloc.c Home-based memory management. * * @author Pekka Pessi . * * @date Created: Thu Aug 19 01:12:25 1999 ppessi */ #include #include "sofia-sip/su_alloc.h" #include "sofia-sip/su_alloc_stat.h" #include "sofia-sip/su_errno.h" #include #include #include #include #include #include int (*_su_home_locker)(void *mutex); int (*_su_home_unlocker)(void *mutex); int (*_su_home_mutex_locker)(void *mutex); int (*_su_home_mutex_trylocker)(void *mutex); int (*_su_home_mutex_unlocker)(void *mutex); void (*_su_home_destroy_mutexes)(void *mutex); #if HAVE_FREE_NULL #define safefree(x) free((x)) #else su_inline void safefree(void *b) { b ? free(b) : (void)0; } #endif #define MEMLOCK(h) \ ((void)((h) && (h)->suh_lock ? _su_home_locker((h)->suh_lock) : 0), (h)->suh_blocks) #define UNLOCK(h) ((void)((h) && (h)->suh_lock ? _su_home_unlocker((h)->suh_lock) : 0), NULL) #ifdef NDEBUG #define MEMCHECK 0 #define MEMCHECK_EXTRA 0 #elif !defined(MEMCHECK) /* Default settings for valgrinding */ #define MEMCHECK 1 #define MEMCHECK_EXTRA 0 #elif !defined(MEMCHECK_EXTRA) #define MEMCHECK_EXTRA sizeof (size_t) #endif enum { SUB_N = 31, /**< Initial size */ SUB_N_AUTO = 7, /**< Initial size for autohome */ SUB_P = 29 /**< Secondary probe. * Secondary probe must be relative prime * with all sub_n values */ }; #define ALIGNMENT (8) #define ALIGN(n) (size_t)(((n) + (ALIGNMENT - 1)) & (size_t)~(ALIGNMENT - 1)) #define SIZEBITS (sizeof (unsigned) * 8 - 1) typedef struct { unsigned sua_size:SIZEBITS; /**< Size of the block */ unsigned sua_home:1; /**< Is this another home? */ unsigned :0; void *sua_data; /**< Data pointer */ } su_alloc_t; struct su_block_s { su_home_t *sub_parent; /**< Parent home */ char *sub_preload; /**< Preload area */ su_home_stat_t *sub_stats; /**< Statistics.. */ void (*sub_destructor)(void *); /**< Destructor function */ size_t sub_ref; /**< Reference count */ #define REF_MAX SIZE_MAX size_t sub_used; /**< Number of blocks allocated */ size_t sub_n; /**< Size of hash table */ unsigned sub_prsize:16; /**< Preload size */ unsigned sub_prused:16; /**< Used from preload */ unsigned sub_hauto:1; /**< "Home" is not from malloc */ unsigned sub_auto:1; /**< struct su_block_s is not from malloc */ unsigned sub_preauto:1; /**< Preload is not from malloc */ unsigned sub_auto_all:1; /**< Everything is from stack! */ unsigned :0; su_alloc_t sub_nodes[SUB_N]; /**< Pointers to data/lower blocks */ }; static void su_home_check_blocks(su_block_t const *b); static void su_home_stats_alloc(su_block_t *, void *p, void *preload, size_t size, int zero); static void su_home_stats_free(su_block_t *sub, void *p, void *preload, unsigned size); static void _su_home_deinit(su_home_t *home); #define SU_ALLOC_STATS 1 #if SU_ALLOC_STATS size_t count_su_block_find, count_su_block_find_loop; size_t size_su_block_find, used_su_block_find; size_t max_size_su_block_find, max_used_su_block_find; size_t su_block_find_collision, su_block_find_collision_used, su_block_find_collision_size; #endif su_inline su_alloc_t *su_block_find(su_block_t const *b, void const *p) { size_t h, h0, probe; #if SU_ALLOC_STATS size_t collision = 0; count_su_block_find++; size_su_block_find += b->sub_n; used_su_block_find += b->sub_used; if (b->sub_n > max_size_su_block_find) max_size_su_block_find = b->sub_n; if (b->sub_used > max_used_su_block_find) max_used_su_block_find = b->sub_used; #endif assert(p != NULL); h = h0 = (size_t)((uintptr_t)p % b->sub_n); probe = (b->sub_n > SUB_P) ? SUB_P : 1; do { if (b->sub_nodes[h].sua_data == p) { su_alloc_t const *retval = &b->sub_nodes[h]; return (su_alloc_t *)retval; /* discard const */ } h += probe; if (h >= b->sub_n) h -= b->sub_n; #if SU_ALLOC_STATS if (++collision > su_block_find_collision) su_block_find_collision = collision, su_block_find_collision_used = b->sub_used, su_block_find_collision_size = b->sub_n; count_su_block_find_loop++; #endif } while (h != h0); return NULL; } su_inline su_alloc_t *su_block_add(su_block_t *b, void *p) { size_t h, probe; assert(p != NULL); h = (size_t)((uintptr_t)p % b->sub_n); probe = (b->sub_n > SUB_P) ? SUB_P : 1; while (b->sub_nodes[h].sua_data) { h += probe; if (h >= b->sub_n) h -= b->sub_n; } b->sub_used++; b->sub_nodes[h].sua_data = p; return &b->sub_nodes[h]; } su_inline int su_is_preloaded(su_block_t const *sub, char *data) { return sub->sub_preload && sub->sub_preload <= data && sub->sub_preload + sub->sub_prsize > data; } su_inline int su_alloc_check(su_block_t const *sub, su_alloc_t const *sua) { #if MEMCHECK_EXTRA size_t size, term; assert(sua); if (sua) { size = (size_t)sua->sua_size; memcpy(&term, (char *)sua->sua_data + size, sizeof (term)); assert(size - term == 0); return size - term == 0; } else return 0; #endif return sua != NULL; } /** Allocate the block hash table. * * @internal * * Allocate a block hash table of @a n elements. * * @param home pointer to home object * @param n number of buckets in hash table * * @return * This function returns a pointer to the allocated hash table or * NULL if an error occurred. */ su_inline su_block_t *su_hash_alloc(size_t n) { su_block_t *b = calloc(1, offsetof(su_block_t, sub_nodes[n])); if (b) { /* Implicit su_home_init(); */ b->sub_ref = 1; b->sub_hauto = 1; b->sub_n = n; } return b; } enum sub_zero { do_malloc, do_calloc, do_clone }; /** Allocate a memory block. * * @internal * * Precondition: locked home * * @param home home to allocate * @param sub block structure used to allocate * @param size * @param zero if true, zero allocated block; * if > 1, allocate a subhome * */ static void *sub_alloc(su_home_t *home, su_block_t *sub, size_t size, enum sub_zero zero) { void *data, *preload = NULL; assert (size < (((size_t)1) << SIZEBITS)); if (size >= ((size_t)1) << SIZEBITS) return (void)(errno = ENOMEM), NULL; if (sub == NULL || 3 * sub->sub_used > 2 * sub->sub_n) { /* Resize the hash table */ size_t i, n, n2, used; su_block_t *b2; if (sub) n = home->suh_blocks->sub_n, n2 = 4 * n + 3, used = sub->sub_used; else n = 0, n2 = SUB_N, used = 0; #if 0 printf("su_alloc(home = %p): realloc block hash of size %d\n", home, n2); #endif if (!(b2 = su_hash_alloc(n2))) return NULL; for (i = 0; i < n; i++) { if (sub->sub_nodes[i].sua_data) su_block_add(b2, sub->sub_nodes[i].sua_data)[0] = sub->sub_nodes[i]; } if (sub) { b2->sub_parent = sub->sub_parent; b2->sub_ref = sub->sub_ref; b2->sub_preload = sub->sub_preload; b2->sub_prsize = sub->sub_prsize; b2->sub_prused = sub->sub_prused; b2->sub_hauto = sub->sub_hauto; b2->sub_preauto = sub->sub_preauto; b2->sub_destructor = sub->sub_destructor; /* auto_all is not copied! */ b2->sub_stats = sub->sub_stats; } home->suh_blocks = b2; if (sub && !sub->sub_auto) free(sub); sub = b2; } if (size && sub && zero < do_clone && sub->sub_preload && size <= sub->sub_prsize) { /* Use preloaded memory */ size_t prused = sub->sub_prused + size + MEMCHECK_EXTRA; prused = ALIGN(prused); if (prused <= sub->sub_prsize) { preload = (char *)sub->sub_preload + sub->sub_prused; sub->sub_prused = (unsigned)prused; } } if (preload && zero) data = memset(preload, 0, size); else if (preload) data = preload; else if (zero) data = calloc(1, size + MEMCHECK_EXTRA); else data = malloc(size + MEMCHECK_EXTRA); if (data) { su_alloc_t *sua; #if MEMCHECK_EXTRA size_t term = 0 - size; memcpy((char *)data + size, &term, sizeof (term)); #endif if (!preload) sub->sub_auto_all = 0; if (zero >= do_clone) { /* Prepare cloned home */ su_home_t *subhome = data; assert(preload == 0); subhome->suh_blocks = su_hash_alloc(SUB_N); if (!subhome->suh_blocks) return (void)safefree(data), NULL; subhome->suh_size = (unsigned)size; subhome->suh_blocks->sub_parent = home; subhome->suh_blocks->sub_hauto = 0; } /* OK, add the block to the hash table. */ sua = su_block_add(sub, data); assert(sua); sua->sua_size = (unsigned)size; sua->sua_home = zero > 1; if (sub->sub_stats) su_home_stats_alloc(sub, data, preload, size, zero); } return data; } /**Create a new su_home_t object. * * Create a home object used to collect multiple memory allocations under * one handle. The memory allocations made using this home object is freed * either when this home is destroyed. * * The maximum @a size of a home object is INT_MAX (2 gigabytes). * * @param size size of home object * * The memory home object allocated with su_home_new() can be reclaimed with * su_home_unref(). * * @return * This function returns a pointer to an su_home_t object, or NULL upon * an error. */ void *su_home_new(isize_t size) { su_home_t *home; assert(size >= sizeof (*home)); if (size < sizeof (*home)) return (void)(errno = EINVAL), NULL; else if (size > INT_MAX) return (void)(errno = ENOMEM), NULL; home = calloc(1, size); if (home) { home->suh_size = (int)size; home->suh_blocks = su_hash_alloc(SUB_N); if (home->suh_blocks) home->suh_blocks->sub_hauto = 0; else safefree(home), home = NULL; } return home; } /** Create a new reference to a home object. */ void *su_home_ref(su_home_t const *home) { if (home) { su_block_t *sub = MEMLOCK(home); if (sub == NULL || sub->sub_ref == 0) { assert(sub && sub->sub_ref != 0); UNLOCK(home); return NULL; } if (sub->sub_ref != REF_MAX) sub->sub_ref++; UNLOCK(home); } else su_seterrno(EFAULT); return (void *)home; } /** Set destructor function. * * The destructor function is called after the reference count of a * #su_home_t object reaches zero or a home object is deinitialized, but * before any of the memory areas within the home object are freed. * * @since New in @VERSION_1_12_4. * Earlier versions had su_home_desctructor() (spelling). */ int su_home_destructor(su_home_t *home, void (*destructor)(void *)) { int retval = -1; if (home) { su_block_t *sub = MEMLOCK(home); if (sub && sub->sub_destructor == NULL) { sub->sub_destructor = destructor; retval = 0; } UNLOCK(home); } else su_seterrno(EFAULT); return retval; } #undef su_home_desctructor /** Set destructor function. * * @deprecated The su_home_destructor() was added in @VERSION_1_12_4. The * su_home_desctructor() is now defined as a macro expanding as * su_home_destructor(). If you want to compile an application as binary * compatible with earlier versions, you have to define su_home_desctructor * as itself, e.g., * @code * #define su_home_desctructor su_home_desctructor * #include * @endcode */ int su_home_desctructor(su_home_t *home, void (*destructor)(void *)) { return su_home_destructor(home, destructor); } /**Unreference a su_home_t object. * * Decrements the reference count on home object and destroys and frees it * and the memory allocations using it if the reference count reaches 0. * * @param home memory pool object to be unreferenced * * @retval 1 if object was freed * @retval 0 if object is still alive */ int su_home_unref(su_home_t *home) { su_block_t *sub; if (home == NULL) return 0; sub = MEMLOCK(home); if (sub == NULL) { /* Xyzzy */ return 0; } else if (sub->sub_ref == REF_MAX) { UNLOCK(home); return 0; } else if (--sub->sub_ref > 0) { UNLOCK(home); return 0; } else if (sub->sub_parent) { su_home_t *parent = sub->sub_parent; UNLOCK(home); su_free(parent, home); return 1; } else { int hauto = sub->sub_hauto; _su_home_deinit(home); if (!hauto) safefree(home); /* UNLOCK(home); */ return 1; } } /** Return reference count of home. */ size_t su_home_refcount(su_home_t *home) { size_t count = 0; if (home) { su_block_t *sub = MEMLOCK(home); if (sub) count = sub->sub_ref; UNLOCK(home); } return count; } /**Clone a su_home_t object. * * Clone a secondary home object used to collect multiple memoryf * allocations under one handle. The memory is freed either when the cloned * home is destroyed or when the parent home is destroyed. * * An independent * home object is created if NULL is passed as @a parent argument. * * @param parent a parent object (may be NULL) * @param size size of home object * * The memory home object allocated with su_home_clone() can be freed with * su_home_unref(). * * @return * This function returns a pointer to an su_home_t object, or NULL upon * an error. */ void *su_home_clone(su_home_t *parent, isize_t size) { su_home_t *home; assert(size >= sizeof (*home)); if (size < sizeof (*home)) return (void)(errno = EINVAL), NULL; else if (size > INT_MAX) return (void)(errno = ENOMEM), NULL; if (parent) { su_block_t *sub = MEMLOCK(parent); home = sub_alloc(parent, sub, size, (enum sub_zero)2); UNLOCK(parent); } else { home = su_home_new(size); } return home; } /** Return true if home is a clone. */ int su_home_has_parent(su_home_t const *home) { return su_home_parent(home) != NULL; } /** Return home's parent home. */ su_home_t *su_home_parent(su_home_t const *home) { su_home_t *parent = NULL; if (home && home->suh_blocks) { su_block_t *sub = MEMLOCK(home); parent = sub->sub_parent; UNLOCK(home); } return parent; } /** Allocate a memory block. * * Allocates a memory block of a given @a size. * * If @a home is NULL, this function behaves exactly like malloc(). * * @param home pointer to home object * @param size size of the memory block to be allocated * * @return * This function returns a pointer to the allocated memory block or * NULL if an error occurred. */ void *su_alloc(su_home_t *home, isize_t size) { void *data; if (home) { data = sub_alloc(home, MEMLOCK(home), size, (enum sub_zero)0); UNLOCK(home); } else data = malloc(size); return data; } /**Free a memory block. * * Frees a single memory block. The @a home must be the owner of the memory * block (usually the memory home used to allocate the memory block, or NULL * if no home was used). * * @param home pointer to home object * @param data pointer to the memory block to be freed */ void su_free(su_home_t *home, void *data) { if (!data) return; if (home) { su_alloc_t *allocation; su_block_t *sub = MEMLOCK(home); assert(sub); allocation = su_block_find(sub, data); assert(allocation); if (su_alloc_check(sub, allocation)) { void *preloaded = NULL; /* Is this preloaded data? */ if (su_is_preloaded(sub, data)) preloaded = data; if (sub->sub_stats) su_home_stats_free(sub, data, preloaded, allocation->sua_size); if (allocation->sua_home) { su_home_t *subhome = data; su_block_t *sub = MEMLOCK(subhome); assert(sub->sub_ref != REF_MAX); /* assert(sub->sub_ref > 0); */ sub->sub_ref = 0; /* Zap all references */ _su_home_deinit(subhome); } #if MEMCHECK != 0 memset(data, 0xaa, (size_t)allocation->sua_size); #endif memset(allocation, 0, sizeof (*allocation)); sub->sub_used--; if (preloaded) data = NULL; } UNLOCK(home); } safefree(data); } /** Check if pointer has been allocated through home. * * @param home pointer to a memory home * @param data pointer to a memory area possibly allocated though home * * @NEW_1_12_9 */ int su_home_check_alloc(su_home_t const *home, void const *data) { int retval = 0; if (home && data) { su_block_t const *sub = MEMLOCK(home); su_alloc_t *allocation = su_block_find(sub, data); retval = allocation != NULL; UNLOCK(home); } return retval; } /** Check home consistency. * * Ensures that the home structure and all memory blocks allocated through * it are consistent. It can be used to catch memory allocation and usage * errors. * * @param home Pointer to a memory home. */ void su_home_check(su_home_t const *home) { #if MEMCHECK != 0 su_block_t const *b = MEMLOCK(home); su_home_check_blocks(b); UNLOCK(home); #endif } /** Check home blocks. */ static void su_home_check_blocks(su_block_t const *b) { #if MEMCHECK != 0 if (b) { size_t i, used; assert(b->sub_used <= b->sub_n); for (i = 0, used = 0; i < b->sub_n; i++) if (b->sub_nodes[i].sua_data) { su_alloc_check(b, &b->sub_nodes[i]), used++; if (b->sub_nodes[i].sua_home) su_home_check((su_home_t *)b->sub_nodes[i].sua_data); } assert(used == b->sub_used); } #endif } /** * Create an su_home_t object. * * Creates a home object. A home object is used to collect multiple memory * allocations, so that they all can be freed by calling su_home_unref(). * * @return This function returns a pointer to an #su_home_t object, or * NULL upon an error. */ su_home_t *su_home_create(void) { return su_home_new(sizeof(su_home_t)); } /** Destroy a home object * * Frees all memory blocks associated with a home object. Note that the home * object structure is not freed. * * @param home pointer to a home object * * @deprecated * su_home_destroy() is included for backwards compatibility only. Use * su_home_unref() instead of su_home_destroy(). */ void su_home_destroy(su_home_t *home) { if (MEMLOCK(home)) { assert(home->suh_blocks); assert(home->suh_blocks->sub_ref == 1); if (!home->suh_blocks->sub_hauto) /* should warn user */; home->suh_blocks->sub_hauto = 1; _su_home_deinit(home); /* UNLOCK(home); */ } } /** Initialize an su_home_t struct. * * Initializes an su_home_t structure. It can be used when the home * structure is allocated from stack or when the home structure is part of * an another object. * * @param home pointer to home object * * @retval 0 when successful * @retval -1 upon an error. * * @sa SU_HOME_INIT(), su_home_deinit(), su_home_new(), su_home_clone() * * @bug * Prior to @VERSION_1_12_8 the su_home_t structure should have been * initialized with SU_HOME_INIT() or otherwise zeroed before calling * su_home_init(). */ int su_home_init(su_home_t *home) { su_block_t *sub; if (home == NULL) return -1; home->suh_blocks = sub = su_hash_alloc(SUB_N); home->suh_lock = NULL; if (!sub) return -1; return 0; } /** Internal deinitialization */ static void _su_home_deinit(su_home_t *home) { if (home->suh_blocks) { size_t i; su_block_t *b; void *suh_lock = home->suh_lock; home->suh_lock = NULL; if (home->suh_blocks->sub_destructor) { void (*destructor)(void *) = home->suh_blocks->sub_destructor; home->suh_blocks->sub_destructor = NULL; destructor(home); } b = home->suh_blocks; su_home_check_blocks(b); for (i = 0; i < b->sub_n; i++) { if (b->sub_nodes[i].sua_data) { if (b->sub_nodes[i].sua_home) { su_home_t *subhome = b->sub_nodes[i].sua_data; su_block_t *subb = MEMLOCK(subhome); assert(subb); assert(subb->sub_ref >= 1); #if 0 if (subb->sub_ref > 0) SU_DEBUG_7(("su_home_unref: subhome %p with destructor %p has still %u refs\n", subhome, subb->sub_destructor, subb->sub_ref)); #endif subb->sub_ref = 0; /* zap them all */ _su_home_deinit(subhome); } else if (su_is_preloaded(b, b->sub_nodes[i].sua_data)) continue; safefree(b->sub_nodes[i].sua_data); } } if (b->sub_preload && !b->sub_preauto) free(b->sub_preload); if (b->sub_stats) free(b->sub_stats); if (!b->sub_auto) free(b); home->suh_blocks = NULL; if (suh_lock) { /* Unlock, or risk assert() or leak handles on Windows */ _su_home_unlocker(suh_lock); _su_home_destroy_mutexes(suh_lock); } } } /** Free memory blocks allocated through home. * * Frees the memory blocks associated with the home object allocated. It * does not free the home object itself. Use su_home_unref() to free the * home object. * * @param home pointer to home object * * @sa su_home_init() */ void su_home_deinit(su_home_t *home) { if (MEMLOCK(home)) { assert(home->suh_blocks); assert(home->suh_blocks->sub_ref == 1); assert(home->suh_blocks->sub_hauto); _su_home_deinit(home); /* UNLOCK(home); */ } } /**Move allocations from a su_home_t object to another. * * Moves allocations made through the @a src home object under the @a dst * home object. It is handy, for example, if an operation allocates some * number of blocks that should be freed upon an error. It uses a temporary * home and moves the blocks from temporary to a proper home when * successful, but frees the temporary home upon an error. * * If @a src has destructor, it is called before starting to move. * * @param dst destination home * @param src source home * * @retval 0 if succesful * @retval -1 upon an error */ int su_home_move(su_home_t *dst, su_home_t *src) { size_t i, n, n2, used; su_block_t *s, *d, *d2; if (src == NULL || dst == src) return 0; if (dst) { s = MEMLOCK(src); d = MEMLOCK(dst); if (s && s->sub_n) { if (s->sub_destructor) { void (*destructor)(void *) = s->sub_destructor; s->sub_destructor = NULL; destructor(src); } if (d) used = s->sub_used + d->sub_used; else used = s->sub_used; if (used && (d == NULL || 3 * used > 2 * d->sub_n)) { if (d) for (n = n2 = d->sub_n; 3 * used > 2 * n2; n2 = 4 * n2 + 3) ; else n = 0, n2 = s->sub_n; if (!(d2 = su_hash_alloc(n2))) { UNLOCK(dst); UNLOCK(src); return -1; } dst->suh_blocks = d2; for (i = 0; i < n; i++) if (d->sub_nodes[i].sua_data) su_block_add(d2, d->sub_nodes[i].sua_data)[0] = d->sub_nodes[i]; if (d) { d2->sub_parent = d->sub_parent; d2->sub_ref = d->sub_ref; d2->sub_preload = d->sub_preload; d2->sub_prsize = d->sub_prsize; d2->sub_prused = d->sub_prused; d2->sub_preauto = d->sub_preauto; d2->sub_stats = d->sub_stats; } if (d && !d->sub_auto) free(d); d = d2; } if (s->sub_used) { n = s->sub_n; for (i = 0; i < n; i++) if (s->sub_nodes[i].sua_data) { su_block_add(d, s->sub_nodes[i].sua_data)[0] = s->sub_nodes[i]; if (s->sub_nodes[i].sua_home) { su_home_t *subhome = s->sub_nodes[i].sua_data; su_block_t *subsub = MEMLOCK(subhome); subsub->sub_parent = dst; UNLOCK(subhome); } } s->sub_used = 0; memset(s->sub_nodes, 0, n * sizeof (s->sub_nodes[0])); } if (s->sub_stats) { /* XXX */ } } UNLOCK(dst); UNLOCK(src); } else { s = MEMLOCK(src); if (s && s->sub_used) { n = s->sub_n; for (i = 0; i < n; i++) { if (s->sub_nodes[i].sua_data && s->sub_nodes[i].sua_home) { su_home_t *subhome = s->sub_nodes[i].sua_data; su_block_t *subsub = MEMLOCK(subhome); subsub->sub_parent = dst; UNLOCK(subhome); } } s->sub_used = 0; memset(s->sub_nodes, 0, s->sub_n * sizeof (s->sub_nodes[0])); s->sub_used = 0; } UNLOCK(src); } return 0; } /** Preload a memory home. * * The function su_home_preload() preloads a memory home. */ void su_home_preload(su_home_t *home, isize_t n, isize_t isize) { su_block_t *sub; if (home == NULL) return; if (home->suh_blocks == NULL) su_home_init(home); sub = MEMLOCK(home); if (!sub->sub_preload) { size_t size; void *preload; size = n * ALIGN(isize); if (size > 65535) /* We have 16 bits... */ size = 65535 & (ALIGNMENT - 1); preload = malloc(size); home->suh_blocks->sub_preload = preload; home->suh_blocks->sub_prsize = (unsigned)size; } UNLOCK(home); } /** Preload a memory home from stack. * * Initializes a memory home using an area allocated from stack. Poor man's * alloca(). */ su_home_t *su_home_auto(void *area, isize_t size) { su_home_t *home; su_block_t *sub; size_t homesize = ALIGN(sizeof *home); size_t subsize = ALIGN(offsetof(su_block_t, sub_nodes[SUB_N_AUTO])); size_t prepsize; char *p = area; prepsize = homesize + subsize + (ALIGN((intptr_t)p) - (intptr_t)p); if (area == NULL || size < prepsize) return NULL; if (size > INT_MAX) size = INT_MAX; home = memset(p, 0, homesize); home->suh_size = (int)size; sub = memset(p + homesize, 0, subsize); home->suh_blocks = sub; if (size > prepsize + 65535) size = prepsize + 65535; sub->sub_n = SUB_N_AUTO; sub->sub_ref = 1; sub->sub_preload = p + prepsize; sub->sub_prsize = (unsigned)(size - prepsize); sub->sub_hauto = 1; sub->sub_auto = 1; sub->sub_preauto = 1; sub->sub_auto_all = 1; return home; } /** Reallocate a memory block. * * Allocates a memory block of @a size bytes. * It copies the old block contents to the new block and frees the old * block. * * If @a home is NULL, this function behaves exactly like realloc(). * * @param home pointer to memory pool object * @param data pointer to old memory block * @param size size of the memory block to be allocated * * @return * A pointer to the allocated memory block or * NULL if an error occurred. */ void *su_realloc(su_home_t *home, void *data, isize_t size) { void *ndata; su_alloc_t *sua; su_block_t *sub; size_t p; size_t term = 0 - size; if (!home) return realloc(data, size); if (size == 0) { if (data) su_free(home, data); return NULL; } sub = MEMLOCK(home); if (!data) { data = sub_alloc(home, sub, size, (enum sub_zero)0); UNLOCK(home); return data; } sua = su_block_find(sub, data); if (!su_alloc_check(sub, sua)) return UNLOCK(home); assert(!sua->sua_home); if (sua->sua_home) return UNLOCK(home); if (!su_is_preloaded(sub, data)) { ndata = realloc(data, size + MEMCHECK_EXTRA); if (ndata) { if (sub->sub_stats) { su_home_stats_free(sub, data, 0, sua->sua_size); su_home_stats_alloc(sub, data, 0, size, 1); } #if MEMCHECK_EXTRA memcpy((char *)ndata + size, &term, sizeof (term)); #else (void)term; #endif memset(sua, 0, sizeof *sua); sub->sub_used--; su_block_add(sub, ndata)->sua_size = (unsigned)size; } UNLOCK(home); return ndata; } p = (char *)data - home->suh_blocks->sub_preload; p += sua->sua_size + MEMCHECK_EXTRA; p = ALIGN(p); if (p == sub->sub_prused) { size_t p2 = (char *)data - sub->sub_preload + size + MEMCHECK_EXTRA; p2 = ALIGN(p2); if (p2 <= sub->sub_prsize) { /* Extend/reduce existing preload */ if (sub->sub_stats) { su_home_stats_free(sub, data, data, sua->sua_size); su_home_stats_alloc(sub, data, data, size, 0); } sub->sub_prused = (unsigned)p2; sua->sua_size = (unsigned)size; #if MEMCHECK_EXTRA memcpy((char *)data + size, &term, sizeof (term)); #endif UNLOCK(home); return data; } } else if (size < (size_t)sua->sua_size) { /* Reduce existing preload */ if (sub->sub_stats) { su_home_stats_free(sub, data, data, sua->sua_size); su_home_stats_alloc(sub, data, data, size, 0); } #if MEMCHECK_EXTRA memcpy((char *)data + size, &term, sizeof (term)); #endif sua->sua_size = (unsigned)size; UNLOCK(home); return data; } ndata = malloc(size + MEMCHECK_EXTRA); if (ndata) { if (p == sub->sub_prused) { /* Free preload */ sub->sub_prused = (char *)data - home->suh_blocks->sub_preload; if (sub->sub_stats) su_home_stats_free(sub, data, data, sua->sua_size); } memcpy(ndata, data, (size_t)sua->sua_size < size ? (size_t)sua->sua_size : size); #if MEMCHECK_EXTRA memcpy((char *)ndata + size, &term, sizeof (term)); #endif if (sub->sub_stats) su_home_stats_alloc(sub, data, 0, size, 1); memset(sua, 0, sizeof *sua); sub->sub_used--; su_block_add(sub, ndata)->sua_size = (unsigned)size; } UNLOCK(home); return ndata; } /**Check if a memory block has been allocated from the @a home. * * Check if the given memory block has been allocated from the home. * * @param home pointer to memory pool object * @param memory ponter to memory block * * @retval 1 if @a memory has been allocated from @a home. * @retval 0 otherwise * * @since New in @VERSION_1_12_4. */ int su_in_home(su_home_t *home, void const *memory) { su_alloc_t *sua; su_block_t *sub; int retval = 0; if (!home || !memory) return 0; sub = MEMLOCK(home); if (sub) { sua = su_block_find(sub, memory); retval = su_alloc_check(sub, sua); UNLOCK(home); } return retval; } /**Allocate and zero a memory block. * * Allocates a memory block with a given size from * given memory home @a home and zeroes the allocated block. * * @param home pointer to memory pool object * @param size size of the memory block * * @note The memory home pointer @a home may be @c NULL. In that case, the * allocated memory block is not associated with any memory home, and it * must be freed by calling su_free() or free(). * * @return * The function su_zalloc() returns a pointer to the allocated memory block, * or NULL upon an error. */ void *su_zalloc(su_home_t *home, isize_t size) { void *data; assert (size >= 0); if (home) { data = sub_alloc(home, MEMLOCK(home), size, (enum sub_zero)1); UNLOCK(home); } else data = calloc(1, size); return data; } /** Allocate a structure * * Allocates a structure with a given size, zeros * it, and initializes the size field to the given size. The size field * is an int at the beginning of the structure. Note that it has type of int. * * @param home pointer to memory pool object * @param size size of the structure * * @par Example * The structure is defined and allocated as follows: * @code * struct test { * int tst_size; * char *tst_name; * void *tst_ptr[3]; * }; * * struct test *t; * ... * t = su_salloc(home, sizeof (*t)); * assert(t && t->t_size == sizeof (*t)); * * @endcode * After calling su_salloc() we get a pointer t to a struct test, * initialized to zero except the tst_size field, which is initialized to * sizeof (*t). * * @return A pointer to the allocated structure, or NULL upon an error. */ void *su_salloc(su_home_t *home, isize_t size) { struct { int size; } *retval; if (size < sizeof (*retval)) size = sizeof (*retval); if (size > INT_MAX) return (void)(errno = ENOMEM), NULL; if (home) { retval = sub_alloc(home, MEMLOCK(home), size, (enum sub_zero)1); UNLOCK(home); } else retval = calloc(1, size); if (retval) retval->size = (int)size; return retval; } /** Check if a memory home is threadsafe */ int su_home_is_threadsafe(su_home_t const *home) { return home && home->suh_lock; } /** Increase refcount and obtain exclusive lock on home. * * @note The #su_home_t structure must be created with su_home_new() or * su_home_clone(), or initialized with su_home_init() before using this * function. * * In order to enable actual locking, use su_home_threadsafe(), too. * Otherwise the su_home_mutex_lock() will just increase the reference * count. */ int su_home_mutex_lock(su_home_t *home) { int error; if (home == NULL) return su_seterrno(EFAULT); if (home->suh_blocks == NULL || !su_home_ref(home)) return su_seterrno(EINVAL); /* Uninitialized home */ if (!home->suh_lock) return 0; /* No-op */ error = _su_home_mutex_locker(home->suh_lock); if (error) return su_seterrno(error); return 0; } /** Release exclusive lock on home and decrease refcount (if home is threadsafe). * * @sa su_home_unlock(). */ int su_home_mutex_unlock(su_home_t *home) { if (home == NULL) return su_seterrno(EFAULT); if (home->suh_lock) { int error = _su_home_mutex_unlocker(home->suh_lock); if (error) return su_seterrno(error); } if (home->suh_blocks == NULL) return su_seterrno(EINVAL), -1; /* Uninitialized home */ su_home_unref(home); return 0; } /** Obtain exclusive lock on home without increasing refcount. * * Unless su_home_threadsafe() has been used to intialize locking on home * object the function just returns -1. * * @return 0 if successful, -1 if not threadsafe, error code otherwise. * * @sa su_home_mutex_lock(), su_home_unlock(), su_home_trylock(). * * @NEW_1_12_8 */ int su_home_lock(su_home_t *home) { if (home == NULL) return EFAULT; if (home->suh_lock == NULL) return -1; /* No-op */ return _su_home_mutex_locker(home->suh_lock); } /** Try to obtain exclusive lock on home without increasing refcount. * * @return 0 if successful, -1 if not threadsafe, * EBUSY if already locked, error code otherwise. * * @sa su_home_lock(), su_home_unlock(). * * @NEW_1_12_8 */ int su_home_trylock(su_home_t *home) { if (home == NULL) return EFAULT; if (home->suh_lock == NULL) return -1; /* No-op */ return _su_home_mutex_trylocker(home->suh_lock); } /** Release exclusive lock on home. * * Release lock without decreasing refcount. * * @return 0 if successful, -1 if not threadsafe, error code otherwise. * * @sa su_home_lock(), su_home_trylock(), su_home_mutex_unlock(). * * @NEW_1_12_8 */ int su_home_unlock(su_home_t *home) { if (home == NULL) return EFAULT; if (home->suh_lock == NULL) return -1; /* No-op */ return _su_home_mutex_unlocker(home->suh_lock); } /** Initialize statistics structure */ void su_home_init_stats(su_home_t *home) { su_block_t *sub; size_t size; if (home == NULL) return; sub = home->suh_blocks; if (!sub) sub = home->suh_blocks = su_hash_alloc(SUB_N); if (!sub) return; if (!sub->sub_stats) { size = sizeof (*sub->sub_stats); sub->sub_stats = malloc(size); if (!sub->sub_stats) return; } else size = sub->sub_stats->hs_size; memset(sub->sub_stats, 0, size); sub->sub_stats->hs_size = (int)size; sub->sub_stats->hs_blocksize = sub->sub_n; } /** Retrieve statistics from memory home. */ void su_home_get_stats(su_home_t *home, int include_clones, su_home_stat_t *hs, isize_t size) { su_block_t *sub; if (hs == NULL || size < (sizeof hs->hs_size)) return; memset((void *)hs, 0, size); sub = MEMLOCK(home); if (sub && sub->sub_stats) { int sub_size = sub->sub_stats->hs_size; if (sub_size > (int)size) sub_size = (int)size; sub->sub_stats->hs_preload.hsp_size = sub->sub_prsize; sub->sub_stats->hs_preload.hsp_used = sub->sub_prused; memcpy(hs, sub->sub_stats, sub_size); hs->hs_size = sub_size; } UNLOCK(home); } static void su_home_stats_alloc(su_block_t *sub, void *p, void *preload, size_t size, int zero) { su_home_stat_t *hs = sub->sub_stats; size_t rsize = ALIGN(size); hs->hs_rehash += (sub->sub_n != hs->hs_blocksize); hs->hs_blocksize = sub->sub_n; hs->hs_clones += zero > 1; if (preload) { hs->hs_allocs.hsa_preload++; return; } hs->hs_allocs.hsa_number++; hs->hs_allocs.hsa_bytes += size; hs->hs_allocs.hsa_rbytes += rsize; if (hs->hs_allocs.hsa_rbytes > hs->hs_allocs.hsa_maxrbytes) hs->hs_allocs.hsa_maxrbytes = hs->hs_allocs.hsa_rbytes; hs->hs_blocks.hsb_number++; hs->hs_blocks.hsb_bytes += size; hs->hs_blocks.hsb_rbytes += rsize; } static void su_home_stats_free(su_block_t *sub, void *p, void *preload, unsigned size) { su_home_stat_t *hs = sub->sub_stats; size_t rsize = ALIGN(size); if (preload) { hs->hs_frees.hsf_preload++; return; } hs->hs_frees.hsf_number++; hs->hs_frees.hsf_bytes += size; hs->hs_frees.hsf_rbytes += rsize; hs->hs_blocks.hsb_number--; hs->hs_blocks.hsb_bytes -= size; hs->hs_blocks.hsb_rbytes -= rsize; } void su_home_stat_add(su_home_stat_t total[1], su_home_stat_t const hs[1]) { total->hs_clones += hs->hs_clones; total->hs_rehash += hs->hs_rehash; if (total->hs_blocksize < hs->hs_blocksize) total->hs_blocksize = hs->hs_blocksize; total->hs_allocs.hsa_number += hs->hs_allocs.hsa_number; total->hs_allocs.hsa_bytes += hs->hs_allocs.hsa_bytes; total->hs_allocs.hsa_rbytes += hs->hs_allocs.hsa_rbytes; total->hs_allocs.hsa_maxrbytes += hs->hs_allocs.hsa_maxrbytes; total->hs_frees.hsf_number += hs->hs_frees.hsf_number; total->hs_frees.hsf_bytes += hs->hs_frees.hsf_bytes; total->hs_frees.hsf_rbytes += hs->hs_frees.hsf_rbytes; total->hs_blocks.hsb_number += hs->hs_blocks.hsb_number; total->hs_blocks.hsb_bytes += hs->hs_blocks.hsb_bytes; total->hs_blocks.hsb_rbytes += hs->hs_blocks.hsb_rbytes; }