ucx/mempool.c

changeset 888
af685cc9d623
parent 852
83fdf679df99
--- a/ucx/mempool.c	Sun Aug 31 14:39:13 2025 +0200
+++ b/ucx/mempool.c	Sat Nov 08 23:06:11 2025 +0100
@@ -31,44 +31,69 @@
 #include <string.h>
 #include <errno.h>
 
-struct cx_mempool_memory_s {
-    /** The destructor. */
-    cx_destructor_func destructor;
-    /** The actual memory. */
-    char c[];
-};
+static int cx_mempool_ensure_capacity(
+        struct cx_mempool_s *pool,
+        size_t needed_capacity
+) {
+    if (needed_capacity <= pool->capacity) return 0;
+    size_t newcap = pool->capacity >= 1000 ?
+        pool->capacity + 1000 : pool->capacity * 2;
+    size_t newmsize;
+    // LCOV_EXCL_START
+    if (pool->capacity > newcap
+            || cx_szmul(newcap, sizeof(void*), &newmsize)) {
+        errno = EOVERFLOW;
+        return 1;
+    } // LCOV_EXCL_STOP
+    void **newdata = cxRealloc(pool->base_allocator, pool->data, newmsize);
+    if (newdata == NULL) return 1;
+    pool->data = newdata;
+    pool->capacity = newcap;
+    return 0;
+}
 
-static void *cx_mempool_malloc(
+static int cx_mempool_ensure_registered_capacity(
+        struct cx_mempool_s *pool,
+        size_t needed_capacity
+) {
+    if (needed_capacity <= pool->registered_capacity) return 0;
+    // we do not expect so many registrations
+    size_t newcap = pool->registered_capacity + 8;
+    size_t newmsize;
+    // LCOV_EXCL_START
+    if (pool->registered_capacity > newcap || cx_szmul(newcap,
+            sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) {
+        errno = EOVERFLOW;
+        return 1;
+    } // LCOV_EXCL_STOP
+    void *newdata = cxRealloc(pool->base_allocator, pool->registered, newmsize);
+    if (newdata == NULL) return 1;
+    pool->registered = newdata;
+    pool->registered_capacity = newcap;
+    return 0;
+}
+
+static void *cx_mempool_malloc_simple(
         void *p,
         size_t n
 ) {
     struct cx_mempool_s *pool = p;
 
-    if (pool->size >= pool->capacity) {
-        size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
-        size_t newmsize;
-        if (pool->capacity > newcap || cx_szmul(newcap,
-                sizeof(struct cx_mempool_memory_s*), &newmsize)) {
-            errno = EOVERFLOW;
-            return NULL;
-        }
-        struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize);
-        if (newdata == NULL) return NULL;
-        pool->data = newdata;
-        pool->capacity = newcap;
+    if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+        return NULL; // LCOV_EXCL_LINE
     }
 
-    struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
+    struct cx_mempool_memory_s *mem =
+        cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory_s) + n);
     if (mem == NULL) return NULL;
-
-    mem->destructor = pool->auto_destr;
+    mem->destructor = NULL;
     pool->data[pool->size] = mem;
     pool->size++;
 
     return mem->c;
 }
 
-static void *cx_mempool_calloc(
+static void *cx_mempool_calloc_simple(
         void *p,
         size_t nelem,
         size_t elsize
@@ -78,53 +103,171 @@
         errno = EOVERFLOW;
         return NULL;
     }
-    void *ptr = cx_mempool_malloc(p, msz);
+    void *ptr = cx_mempool_malloc_simple(p, msz);
     if (ptr == NULL) return NULL;
     memset(ptr, 0, nelem * elsize);
     return ptr;
 }
 
-static void *cx_mempool_realloc(
+static void cx_mempool_free_simple(
+        void *p,
+        void *ptr
+) {
+    if (!ptr) return;
+    struct cx_mempool_s *pool = p;
+
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
+    struct cx_mempool_memory_s *mem =
+        (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s));
+
+    for (size_t i = 0; i < pool->size; i++) {
+        if (mem == pool->data[i]) {
+            if (mem->destructor) {
+                mem->destructor(mem->c);
+            }
+            if (destr != NULL) {
+                destr(mem->c);
+            }
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, mem->c);
+            }
+            cxFree(pool->base_allocator, mem);
+            size_t last_index = pool->size - 1;
+            if (i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->size--;
+            return;
+        }
+    }
+    abort(); // LCOV_EXCL_LINE
+}
+
+static void *cx_mempool_realloc_simple(
         void *p,
         void *ptr,
         size_t n
 ) {
+    if (ptr == NULL) {
+        return cx_mempool_malloc_simple(p, n);
+    }
+    if (n == 0) {
+        cx_mempool_free_simple(p, ptr);
+        return NULL;
+    }
     struct cx_mempool_s *pool = p;
 
-    struct cx_mempool_memory_s *mem, *newm;
-    mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
-    newm = realloc(mem, n + sizeof(cx_destructor_func));
+    const unsigned overhead = sizeof(struct cx_mempool_memory_s);
+    struct cx_mempool_memory_s *mem =
+        (void *) (((char *) ptr) - overhead);
+    struct cx_mempool_memory_s *newm =
+        cxRealloc(pool->base_allocator, mem, n + overhead);
 
     if (newm == NULL) return NULL;
     if (mem != newm) {
         for (size_t i = 0; i < pool->size; i++) {
             if (pool->data[i] == mem) {
                 pool->data[i] = newm;
-                return ((char*)newm) + sizeof(cx_destructor_func);
+                return ((char*)newm) + overhead;
             }
         }
         abort(); // LCOV_EXCL_LINE
     } else {
-        return ptr;
+        // unfortunately glibc() realloc seems to always move
+        return ptr;  // LCOV_EXCL_LINE
+    }
+}
+
+static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) {
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+    for (size_t i = 0; i < pool->size; i++) {
+        struct cx_mempool_memory_s *mem = pool->data[i];
+        if (mem->destructor) {
+            mem->destructor(mem->c);
+        }
+        if (destr != NULL) {
+            destr(mem->c);
+        }
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem->c);
+        }
+        cxFree(pool->base_allocator, mem);
     }
 }
 
-static void cx_mempool_free(
+static cx_allocator_class cx_mempool_simple_allocator_class = {
+    cx_mempool_malloc_simple,
+    cx_mempool_realloc_simple,
+    cx_mempool_calloc_simple,
+    cx_mempool_free_simple
+};
+
+static void *cx_mempool_malloc_advanced(
+        void *p,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
+
+    if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+        return NULL;  // LCOV_EXCL_LINE
+    }
+
+    struct cx_mempool_memory2_s *mem =
+        cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory2_s) + n);
+    if (mem == NULL) return NULL;
+    mem->destructor = NULL;
+    mem->data = NULL;
+    pool->data[pool->size] = mem;
+    pool->size++;
+
+    return mem->c;
+}
+
+static void *cx_mempool_calloc_advanced(
+        void *p,
+        size_t nelem,
+        size_t elsize
+) {
+    size_t msz;
+    if (cx_szmul(nelem, elsize, &msz)) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+    void *ptr = cx_mempool_malloc_advanced(p, msz);
+    if (ptr == NULL) return NULL;
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+static void cx_mempool_free_advanced(
         void *p,
         void *ptr
 ) {
     if (!ptr) return;
     struct cx_mempool_s *pool = p;
 
-    struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
-            ((char *) ptr - sizeof(cx_destructor_func));
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
+    struct cx_mempool_memory2_s *mem =
+        (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s));
 
     for (size_t i = 0; i < pool->size; i++) {
         if (mem == pool->data[i]) {
             if (mem->destructor) {
-                mem->destructor(mem->c);
+                mem->destructor(mem->data, mem->c);
+            }
+            if (destr != NULL) {
+                destr(mem->c);
             }
-            free(mem);
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, mem->c);
+            }
+            cxFree(pool->base_allocator, mem);
             size_t last_index = pool->size - 1;
             if (i != last_index) {
                 pool->data[i] = pool->data[last_index];
@@ -137,19 +280,210 @@
     abort(); // LCOV_EXCL_LINE
 }
 
+static void *cx_mempool_realloc_advanced(
+        void *p,
+        void *ptr,
+        size_t n
+) {
+    if (ptr == NULL) {
+        return cx_mempool_malloc_advanced(p, n);
+    }
+    if (n == 0) {
+        cx_mempool_free_advanced(p, ptr);
+        return NULL;
+    }
+    struct cx_mempool_s *pool = p;
+
+    const unsigned overhead = sizeof(struct cx_mempool_memory2_s);
+    struct cx_mempool_memory2_s *mem =
+        (void *) (((char *) ptr) - overhead);
+    struct cx_mempool_memory2_s *newm =
+        cxRealloc(pool->base_allocator, mem, n + overhead);
+
+    if (newm == NULL) return NULL;
+    if (mem != newm) {
+        for (size_t i = 0; i < pool->size; i++) {
+            if (pool->data[i] == mem) {
+                pool->data[i] = newm;
+                return ((char*)newm) + overhead;
+            }
+        }
+        abort(); // LCOV_EXCL_LINE
+    } else {
+        // unfortunately glibc() realloc seems to always move
+        return ptr;  // LCOV_EXCL_LINE
+    }
+}
+
+static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) {
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+    for (size_t i = 0; i < pool->size; i++) {
+        struct cx_mempool_memory2_s *mem = pool->data[i];
+        if (mem->destructor) {
+            mem->destructor(mem->data, mem->c);
+        }
+        if (destr != NULL) {
+            destr(mem->c);
+        }
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem->c);
+        }
+        cxFree(pool->base_allocator, mem);
+    }
+}
+
+static cx_allocator_class cx_mempool_advanced_allocator_class = {
+    cx_mempool_malloc_advanced,
+    cx_mempool_realloc_advanced,
+    cx_mempool_calloc_advanced,
+    cx_mempool_free_advanced
+};
+
+
+static void *cx_mempool_malloc_pure(
+        void *p,
+        size_t n
+) {
+    struct cx_mempool_s *pool = p;
+
+    if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+        return NULL; // LCOV_EXCL_LINE
+    }
+
+    void *mem = cxMalloc(pool->base_allocator, n);
+    if (mem == NULL) return NULL;
+    pool->data[pool->size] = mem;
+    pool->size++;
+
+    return mem;
+}
+
+static void *cx_mempool_calloc_pure(
+        void *p,
+        size_t nelem,
+        size_t elsize
+) {
+    size_t msz;
+    if (cx_szmul(nelem, elsize, &msz)) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+    void *ptr = cx_mempool_malloc_pure(p, msz);
+    if (ptr == NULL) return NULL;
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+static void cx_mempool_free_pure(
+        void *p,
+        void *ptr
+) {
+    if (!ptr) return;
+    struct cx_mempool_s *pool = p;
+
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+
+    for (size_t i = 0; i < pool->size; i++) {
+        if (ptr == pool->data[i]) {
+            if (destr != NULL) {
+                destr(ptr);
+            }
+            if (destr2 != NULL) {
+                destr2(pool->destr2_data, ptr);
+            }
+            cxFree(pool->base_allocator, ptr);
+            size_t last_index = pool->size - 1;
+            if (i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->size--;
+            return;
+        }
+    }
+    abort(); // LCOV_EXCL_LINE
+}
+
+static void *cx_mempool_realloc_pure(
+        void *p,
+        void *ptr,
+        size_t n
+) {
+    if (ptr == NULL) {
+        return cx_mempool_malloc_pure(p, n);
+    }
+    if (n == 0) {
+        cx_mempool_free_pure(p, ptr);
+        return NULL;
+    }
+    struct cx_mempool_s *pool = p;
+    void *newm = cxRealloc(pool->base_allocator, ptr, n);
+    if (newm == NULL) return NULL;
+    if (ptr != newm) {
+        for (size_t i = 0; i < pool->size; i++) {
+            if (pool->data[i] == ptr) {
+                pool->data[i] = newm;
+                return newm;
+            }
+        }
+        abort(); // LCOV_EXCL_LINE
+    } else {
+        // unfortunately glibc() realloc seems to always move
+        return ptr;  // LCOV_EXCL_LINE
+    }
+}
+
+static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) {
+    cx_destructor_func destr = pool->destr;
+    cx_destructor_func2 destr2 = pool->destr2;
+    for (size_t i = 0; i < pool->size; i++) {
+        void *mem = pool->data[i];
+        if (destr != NULL) {
+            destr(mem);
+        }
+        if (destr2 != NULL) {
+            destr2(pool->destr2_data, mem);
+        }
+        cxFree(pool->base_allocator, mem);
+    }
+}
+
+static cx_allocator_class cx_mempool_pure_allocator_class = {
+    cx_mempool_malloc_pure,
+    cx_mempool_realloc_pure,
+    cx_mempool_calloc_pure,
+    cx_mempool_free_pure
+};
+
+static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) {
+    for (size_t i = 0; i < pool->registered_size; i++) {
+        struct cx_mempool_foreign_memory_s info = pool->registered[i];
+        if (info.destr2_data == NULL) {
+            if (info.destr) {
+                info.destr(info.mem);
+            }
+        } else {
+            info.destr2(info.destr2_data, info.mem);
+        }
+    }
+}
+
 void cxMempoolFree(CxMempool *pool) {
     if (pool == NULL) return;
-    struct cx_mempool_memory_s *mem;
-    for (size_t i = 0; i < pool->size; i++) {
-        mem = pool->data[i];
-        if (mem->destructor) {
-            mem->destructor(mem->c);
-        }
-        free(mem);
+    if (pool->allocator->cl == &cx_mempool_simple_allocator_class) {
+        cx_mempool_free_all_simple(pool);
+    } else if (pool->allocator->cl == &cx_mempool_advanced_allocator_class) {
+        cx_mempool_free_all_advanced(pool);
+    } else {
+        cx_mempool_free_all_pure(pool);
     }
-    free(pool->data);
-    free((void*) pool->allocator);
-    free(pool);
+    cx_mempool_free_foreign(pool);
+    cxFree(pool->base_allocator, pool->data);
+    cxFree(pool->base_allocator, pool->registered);
+    cxFree(pool->base_allocator, (void*) pool->allocator);
+    cxFree(pool->base_allocator, pool);
 }
 
 void cxMempoolSetDestructor(
@@ -159,18 +493,26 @@
     *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
 }
 
+void cxMempoolSetDestructor2(
+        void *ptr,
+        cx_destructor_func2 func,
+        void *data
+) {
+    struct cx_mempool_memory2_s *info =
+        (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s));
+    info->destructor = func;
+    info->data = data;
+}
+
 void cxMempoolRemoveDestructor(void *ptr) {
     *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL;
 }
 
-struct cx_mempool_foreign_mem_s {
-    cx_destructor_func destr;
-    void* mem;
-};
-
-static void cx_mempool_destr_foreign_mem(void* ptr) {
-    struct cx_mempool_foreign_mem_s *fm = ptr;
-    fm->destr(fm->mem);
+void cxMempoolRemoveDestructor2(void *ptr) {
+    struct cx_mempool_memory2_s *info =
+        (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s));
+    info->destructor = NULL;
+    info->data = NULL;
 }
 
 int cxMempoolRegister(
@@ -178,60 +520,212 @@
         void *memory,
         cx_destructor_func destr
 ) {
-    struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
-            pool,
-            sizeof(struct cx_mempool_foreign_mem_s)
-    );
-    if (fm == NULL) return 1;
+    if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) {
+        return 1; // LCOV_EXCL_LINE
+    }
+
+    pool->registered[pool->registered_size++] =
+        (struct cx_mempool_foreign_memory_s) {
+            .mem = memory,
+            .destr = destr,
+            .destr2_data = NULL
+        };
+
+    return 0;
+}
 
-    fm->mem = memory;
-    fm->destr = destr;
-    *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+int cxMempoolRegister2(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func2 destr,
+        void *data
+) {
+    if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) {
+        return 1; // LCOV_EXCL_LINE
+    }
+
+    pool->registered[pool->registered_size++] =
+        (struct cx_mempool_foreign_memory_s) {
+            .mem = memory,
+            .destr2 = destr,
+            .destr2_data = data
+        };
 
     return 0;
 }
 
-static cx_allocator_class cx_mempool_allocator_class = {
-        cx_mempool_malloc,
-        cx_mempool_realloc,
-        cx_mempool_calloc,
-        cx_mempool_free
-};
-
 CxMempool *cxMempoolCreate(
         size_t capacity,
-        cx_destructor_func destr
+        enum cx_mempool_type type
 ) {
+    if (capacity == 0) capacity = 16;
     size_t poolsize;
-    if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+    if (cx_szmul(capacity, sizeof(void*), &poolsize)) {
+        // LCOV_EXCL_START
         errno = EOVERFLOW;
         return NULL;
+    }  // LCOV_EXCL_STOP
+
+    CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator));
+    if (provided_allocator == NULL) { // LCOV_EXCL_START
+        return NULL;
+    } // LCOV_EXCL_STOP
+
+    CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool));
+    if (pool == NULL) {  // LCOV_EXCL_START
+        cxFreeDefault(provided_allocator);
+        return NULL;
+    }  // LCOV_EXCL_STOP
+
+    provided_allocator->data = pool;
+    *((const CxAllocator**)&pool->base_allocator) = cxDefaultAllocator;
+    pool->allocator = provided_allocator;
+    if (type == CX_MEMPOOL_TYPE_SIMPLE) {
+        provided_allocator->cl = &cx_mempool_simple_allocator_class;
+    } else if (type == CX_MEMPOOL_TYPE_ADVANCED) {
+        provided_allocator->cl = &cx_mempool_advanced_allocator_class;
+    } else {
+        provided_allocator->cl = &cx_mempool_pure_allocator_class;
     }
 
-    struct cx_mempool_s *pool =
-            malloc(sizeof(struct cx_mempool_s));
-    if (pool == NULL) return NULL;
-
-    CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
-    if (provided_allocator == NULL) { // LCOV_EXCL_START
-        free(pool);
-        return NULL;
-    } // LCOV_EXCL_STOP
-    provided_allocator->cl = &cx_mempool_allocator_class;
-    provided_allocator->data = pool;
-
-    pool->allocator = provided_allocator;
-
-    pool->data = malloc(poolsize);
+    pool->data = cxMallocDefault(poolsize);
     if (pool->data == NULL) { // LCOV_EXCL_START
-        free(provided_allocator);
-        free(pool);
+        cxFreeDefault(provided_allocator);
+        cxFreeDefault(pool);
         return NULL;
     } // LCOV_EXCL_STOP
 
     pool->size = 0;
     pool->capacity = capacity;
-    pool->auto_destr = destr;
 
     return pool;
 }
+
+void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc) {
+    pool->destr = fnc;
+}
+
+void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data) {
+    pool->destr2 = fnc;
+    pool->destr2_data = data;
+}
+
+static void cx_mempool_free_transferred_allocator(void *base_al, void *al) {
+    cxFree(base_al, al);
+}
+
+int cxMempoolTransfer(
+        CxMempool *source,
+        CxMempool *dest
+) {
+    // safety checks
+    if (source == dest) return 1;
+    if (source->allocator->cl != dest->allocator->cl) return 1;
+    if (source->base_allocator->cl != dest->base_allocator->cl) return 1;
+
+    // ensure enough capacity in the destination pool
+    if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) {
+        return 1; // LCOV_EXCL_LINE
+    }
+    if (cx_mempool_ensure_registered_capacity(dest,
+            dest->registered_size + source->registered_size)) {
+        return 1; // LCOV_EXCL_LINE
+    }
+
+    // allocate a replacement allocator for the source pool
+    CxAllocator *new_source_allocator =
+        cxMalloc(source->base_allocator, sizeof(CxAllocator));
+    if (new_source_allocator == NULL) { // LCOV_EXCL_START
+        return 1;
+    } // LCOV_EXCL_STOP
+    new_source_allocator->cl = source->allocator->cl;
+    new_source_allocator->data = source;
+
+    // transfer all the data
+    if (source->size > 0) {
+        memcpy(&dest->data[dest->size], source->data,
+            sizeof(void*)*source->size);
+        dest->size += source->size;
+    }
+
+    // transfer all registered memory
+    if (source->registered_size > 0) {
+        memcpy(&dest->registered[dest->registered_size], source->registered,
+               sizeof(struct cx_mempool_foreign_memory_s)
+               * source->registered_size);
+        dest->registered_size += source->registered_size;
+    }
+
+    // register the old allocator with the new pool
+    // we have to remove const-ness for this, but that's okay here
+    // also register the base allocator, s.t. the pool knows how to free it
+    CxAllocator *transferred_allocator = (CxAllocator*) source->allocator;
+    transferred_allocator->data = dest;
+    cxMempoolRegister2(dest, transferred_allocator,
+        cx_mempool_free_transferred_allocator, (void*)source->base_allocator);
+
+    // prepare the source pool for re-use
+    source->allocator = new_source_allocator;
+    memset(source->data, 0, source->size * sizeof(void*));
+    memset(source->registered, 0,
+        source->registered_size * sizeof(struct cx_mempool_foreign_memory_s));
+    source->size = 0;
+    source->registered_size = 0;
+
+    return 0;
+}
+
+int cxMempoolTransferObject(
+        CxMempool *source,
+        CxMempool *dest,
+        const void *obj
+) {
+    // safety checks
+    if (source == dest) return 1;
+    if (source->allocator->cl != dest->allocator->cl) return 1;
+    if (source->base_allocator->cl != dest->base_allocator->cl) return 1;
+
+    // search for the object
+    for (size_t i = 0; i < source->size; i++) {
+        struct cx_mempool_memory_s *mem = source->data[i];
+        if (mem->c == obj) {
+            // first, make sure that the dest pool can take the object
+            if (cx_mempool_ensure_capacity(dest, dest->size + 1)) {
+                return 1; // LCOV_EXCL_LINE
+            }
+            // remove from the source pool
+            size_t last_index = source->size - 1;
+            if (i != last_index) {
+                source->data[i] = source->data[last_index];
+                source->data[last_index] = NULL;
+            }
+            source->size--;
+            // add to the target pool
+            dest->data[dest->size++] = mem;
+            return 0;
+        }
+    }
+    // search in the registered objects
+    for (size_t i = 0; i < source->registered_size; i++) {
+        struct cx_mempool_foreign_memory_s *mem = &source->registered[i];
+        if (mem->mem == obj) {
+            // first, make sure that the dest pool can take the object
+            if (cx_mempool_ensure_registered_capacity(dest,
+                    dest->registered_size + 1)) {
+                return 1; // LCOV_EXCL_LINE
+            }
+            dest->registered[dest->registered_size++] = *mem;
+            // remove from the source pool
+            size_t last_index = source->registered_size - 1;
+            if (i != last_index) {
+                source->registered[i] = source->registered[last_index];
+                memset(&source->registered[last_index], 0,
+                    sizeof(struct cx_mempool_foreign_memory_s));
+            }
+            source->registered_size--;
+            return 0;
+        }
+    }
+    // not found
+    return 1;
+}

mercurial