From 2a016de1c2eb45fc5f9c8cebf6b3c726b01ec340 Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Tue, 11 Jul 2023 18:37:31 -0700
Subject: Add support for dynamically allocated mempools.

---
 mempool/include/mempool.h | 76 +++++++++++++++++++++++++++++++++++------------
 mempool/src/mempool.c     | 44 ++++++++++++++++++++++-----
 2 files changed, 94 insertions(+), 26 deletions(-)

diff --git a/mempool/include/mempool.h b/mempool/include/mempool.h
index a0b3a33..2447884 100644
--- a/mempool/include/mempool.h
+++ b/mempool/include/mempool.h
@@ -1,3 +1,16 @@
+/*
+ * Block/Pool Allocator.
+ *
+ * Clients should use the macros to define and use pools. They make the API
+ * type-safe.
+ *
+ * The pool allocator works on one big chunk of memory, which can be statically
+ * or dynamically allocated. This chunk is divided into fixed-sized blocks.
+ * Allocation/deallocation happens with block granularity.
+ *
+ * Block information is stored in a separate array so that client data is
+ * contiguous in the main pool of memory and better cached.
+ */
 #pragma once
 
 #include <assert.h>
@@ -5,7 +18,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-/// Define a typed mempool of the given number of blocks.
+/// Define a statically-allocated, typed pool of the given number of blocks.
 #define DEF_MEMPOOL(POOL, TYPE, NUM_BLOCKS) \
   typedef struct POOL {                     \
     mempool   pool;                         \
@@ -13,7 +26,15 @@
     TYPE      blocks[NUM_BLOCKS];           \
   } POOL;
 
-/// Create a new pool.
+/// Define a dynamically-allocated, typed pool.
+#define DEF_MEMPOOL_DYN(POOL, TYPE) \
+  typedef struct POOL {             \
+    mempool    pool;                \
+    BlockInfo* block_info;          \
+    TYPE*      blocks;              \
+  } POOL;
+
+/// Initialize a statically-allocated pool.
 #define mempool_make(POOL)                                             \
   {                                                                    \
     assert(POOL);                                                      \
@@ -24,8 +45,24 @@
         block_size);                                                   \
   }
 
+/// Initialize a dynamically-allocated pool.
+#define mempool_make_dyn(POOL, num_blocks, block_size) \
+  mempool_make_(&(POOL)->pool, 0, 0, num_blocks, block_size)
+
+/// Destroy the pool.
+///
+/// If the pool is dynamically allocated, then this function frees its memory.
+#define mempool_del(POOL) mempool_del_(&(POOL)->pool)
+
+/// Clear the pool.
+///
+/// This function frees all of the pool's blocks. The resulting pool is as if it
+/// were newly created.
+#define mempool_clear(POOL) mempool_clear_(&(POOL)->pool)
+
 /// Allocate a new block.
 /// Return 0 if there is no memory left.
+/// New blocks are conveniently zeroed out.
 #define mempool_alloc(POOL) mempool_alloc_(&(POOL)->pool)
 
 /// Free the block.
@@ -60,6 +97,8 @@
     BODY;                                                                    \
   }
 
+// -----------------------------------------------------------------------------
+
 typedef struct BlockInfo {
   bool used;
 } BlockInfo;
@@ -69,29 +108,28 @@ typedef struct mempool {
   size_t     num_blocks;
   size_t     next_free_block;
   bool       full;
+  bool       dynamic; // True if blocks and info are dynamically-allocated.
   BlockInfo* block_info;
   uint8_t*   blocks;
 } mempool;
 
-/// Create a pool allocator from user-provided memory.
-/// `BlockInfo` must hold at least `num_blocks` entries.
-/// `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
+/// Create a pool allocator.
+///
+/// 'BlockInfo' and 'blocks' may be user-provided (static pool) or null (dynamic
+/// pool).
+/// - If null, the pool malloc()s the memory for them.
+/// - If given:
+///   - `BlockInfo` must hold at least `num_blocks` entries.
+///   - `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
+///
 /// All blocks are zeroed out for convenience.
-void mempool_make_(
+bool mempool_make_(
     mempool*, BlockInfo*, void* blocks, size_t num_blocks,
     size_t block_size_bytes);
 
-/// Allocate a new block.
-/// Return 0 if there is no memory left.
-void* mempool_alloc_(mempool*);
-
-/// Free the block.
-/// The block pointer is conveniently set to 0.
-void mempool_free_(mempool*, void** block_ptr);
-
-/// Return the ith block.
-/// The block must have been allocated.
-void* mempool_get_block_(const mempool*, size_t block_index);
-
-/// Get the index to the given block.
+void   mempool_del_(mempool*);
+void   mempool_clear_(mempool*);
+void*  mempool_alloc_(mempool*);
+void   mempool_free_(mempool*, void** block_ptr);
+void*  mempool_get_block_(const mempool*, size_t block_index);
 size_t mempool_get_block_index_(const mempool*, const void* block);
diff --git a/mempool/src/mempool.c b/mempool/src/mempool.c
index b4693a5..059db93 100644
--- a/mempool/src/mempool.c
+++ b/mempool/src/mempool.c
@@ -1,24 +1,54 @@
 #include "mempool.h"
 
+#include <stdlib.h>
 #include <string.h>
 
 static inline size_t min(size_t a, size_t b) { return a < b ? a : b; }
 
-void mempool_make_(
+bool mempool_make_(
     mempool* pool, BlockInfo* block_info, void* blocks, size_t num_blocks,
     size_t block_size_bytes) {
   assert(pool);
-  assert(block_info);
-  assert(blocks);
+  assert((block_info && blocks) || (!block_info && !blocks));
   assert(num_blocks >= 1);
   pool->block_size_bytes = block_size_bytes;
   pool->num_blocks       = num_blocks;
   pool->next_free_block  = 0;
   pool->full             = false;
-  pool->block_info       = block_info;
-  pool->blocks           = blocks;
-  memset(blocks, 0, num_blocks * block_size_bytes);
-  memset(block_info, 0, num_blocks * sizeof(BlockInfo));
+  if (!block_info) {
+    block_info    = calloc(num_blocks, sizeof(BlockInfo));
+    blocks        = calloc(num_blocks, block_size_bytes);
+    pool->dynamic = true;
+  } else {
+    memset(blocks, 0, num_blocks * block_size_bytes);
+    memset(block_info, 0, num_blocks * sizeof(BlockInfo));
+    pool->dynamic = false;
+  }
+  pool->block_info = block_info;
+  pool->blocks     = blocks;
+  return (block_info != 0) && (blocks != 0);
+}
+
+void mempool_del_(mempool* pool) {
+  assert(pool);
+  if (pool->dynamic) {
+    if (pool->block_info) {
+      free(pool->block_info);
+      pool->block_info = 0;
+    }
+    if (pool->blocks) {
+      free(pool->blocks);
+      pool->blocks = 0;
+    }
+  }
+}
+
+void mempool_clear_(mempool* pool) {
+  assert(pool);
+  pool->next_free_block = 0;
+  pool->full            = false;
+  memset(pool->blocks, 0, pool->num_blocks * pool->block_size_bytes);
+  memset(pool->block_info, 0, pool->num_blocks * sizeof(BlockInfo));
 }
 
 void* mempool_alloc_(mempool* pool) {
-- 
cgit v1.2.3