🌊 Sailfish pool (sfpool)
Secure and Fast memory pool, in 300 lines of portable, header-only C code.
Loading...
Searching...
No Matches
sfpool.h
1/* SPDX-FileCopyrightText: 2025 Dyne.org foundation
2 * SPDX-License-Identifier: GPL-3.0-or-later
3 *
4 * Copyright (C) 2025 Dyne.org foundation
5 * designed, written and maintained by Denis Roio <jaromil@dyne.org>
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public
18 * License along with this program. If not, see
19 * <https://www.gnu.org/licenses/>.
20 *
21 */
22
23#ifndef __SFPOOL_H__
24#define __SFPOOL_H__
25
26// This header carries both declarations and implementation.
27// All functions use internal linkage, so it is safe to include it
28// from multiple translation units in the same program.
29
30#include <stdlib.h>
31#include <string.h>
32#include <stdio.h>
33#include <stdint.h>
34#include <stdbool.h>
35#include <assert.h>
36#ifdef __EMSCRIPTEN__
37#include <emscripten.h>
38#elif defined(_WIN32)
39#include <windows.h>
40#else // lucky shot on POSIX
41// defined(__unix__) || defined(__linux__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
42#include <unistd.h> // for geteuid to switch protected memory map
43#include <sys/resource.h>
44#include <sys/mman.h>
45#endif
46
47// Configuration
48#define SECURE_ZERO // Enable secure zeroing
49#define PROFILING // Profile most used sizes allocated
50
51#if defined(__x86_64__) || defined(_M_X64) || defined(__ppc64__) || defined(__LP64__)
52#define ptr_t uint64_t
53#define ptr_align 8
54#define struct_align 16
55#else
56#define ptr_t uint32_t
57#define ptr_align 4
58#define struct_align 8
59#endif
60
61// Memory pool structure
62typedef struct __attribute__((aligned(struct_align))) sfpool_t {
63 uint8_t *buffer; // raw
64 uint8_t *data; // aligned
65 uint8_t *free_list;
66 uint32_t free_count;
67 uint32_t total_blocks;
68 uint32_t total_bytes;
69 uint32_t block_size;
70#ifdef PROFILING
71 uint32_t *hits;
72 uint32_t hits_total;
73 size_t hits_bytes;
74 uint32_t miss_total;
75 size_t miss_bytes;
76 size_t alloc_total;
77#endif
78} sfpool_t;
79
80
81#if !defined(__MUSL__)
82static_assert(sizeof(ptr_t) == sizeof(void*), "Unknown memory pointer size detected");
83#endif
84static inline bool _is_in_pool(sfpool_t *pool, const void *ptr) {
85 volatile ptr_t p = (ptr_t)ptr;
86 return(p >= (ptr_t)pool->data
87 && p < (ptr_t)(pool->data + pool->total_bytes));
88}
89
103static inline void sfutil_zero(void *ptr, uint32_t size) {
104 volatile uint8_t *p = (volatile uint8_t*)ptr;
105 while (size--) *p++ = 0;
106}
107
116static inline void *sfutil_memalign(const void* ptr) {
117 register ptr_t mask = ptr_align - 1;
118 ptr_t aligned = ((ptr_t)ptr + mask) & ~mask;
119 return (void*)aligned;
120}
121
130static inline void *sfutil_secalloc(size_t size) {
131 // add bytes to every allocation to support alignment
132 size_t alloc_size = size + ptr_align;
133 void *res = NULL;
134#if defined(__EMSCRIPTEN__)
135 res = (uint8_t *)malloc(alloc_size);
136#elif defined(_WIN32)
137 res = VirtualAlloc(NULL, alloc_size,
138 MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
139#elif defined(__APPLE__)
140 int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
141 res = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, flags, -1, 0);
142 if (res == MAP_FAILED) return NULL;
143 mlock(res, alloc_size);
144#else // assume POSIX
145 int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
146 struct rlimit rl;
147 if (getrlimit(RLIMIT_MEMLOCK, &rl) == 0)
148 if(alloc_size<=rl.rlim_cur) flags |= MAP_LOCKED;
149 res = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, flags, -1, 0);
150 if (res == MAP_FAILED) return NULL;
151#endif
152 return res;
153}
154
163static inline void sfutil_secfree(void *ptr, size_t size) {
164 size_t alloc_size = size + ptr_align;
165#if defined(__EMSCRIPTEN__)
166 free(ptr);
167#elif defined(_WIN32)
168 VirtualFree(ptr, 0, MEM_RELEASE);
169#else // Posix
170 munmap(ptr, alloc_size);
171#endif
172}
173
// End of sfutil group
175
176
193static inline size_t sfpool_init(sfpool_t *pool, size_t nmemb, size_t blocksize) {
194 if (pool == NULL) return 0;
195 memset(pool, 0, sizeof(sfpool_t));
196 if (nmemb == 0) return 0;
197 if (blocksize < sizeof(void*)) return 0;
198 if((blocksize & (blocksize - 1)) != 0) return 0;
199 if (nmemb > (SIZE_MAX / blocksize)) return 0;
200 // SFPool blocksize must be a power of two
201 size_t totalsize = nmemb * blocksize;
202 pool->buffer = sfutil_secalloc(totalsize);
203 if (pool->buffer == NULL) return 0;
204 // Failed to allocate pool memory
205 pool->data = sfutil_memalign(pool->buffer);
206 if (pool->data == NULL) return 0;
207 // Failed to allocate pool memory
208 pool->total_bytes = totalsize;
209 pool->total_blocks = nmemb;
210 pool->block_size = blocksize;
211 // Initialize the embedded free list
212 pool->free_list = pool->data;
213 register uint32_t i, bi;
214 for (i = 0; i < pool->total_blocks - 1; ++i) {
215 bi = i*blocksize;
216 *(uint8_t **)(pool->data + bi) =
217 pool->data + bi + blocksize;
218 }
219 pool->free_count = pool->total_blocks;
220 *(uint8_t **)
221 (pool->data + (pool->total_blocks - 1) * blocksize) = NULL;
222#ifdef PROFILING
223 pool->miss_total = pool->miss_bytes = 0;
224 pool->hits_total = pool->hits_bytes = 0;
225 pool->alloc_total = 0;
226#endif
227 return totalsize;
228}
229
230
238static inline void sfpool_teardown(sfpool_t *restrict pool) {
239 // Free pool memory
240 sfutil_secfree(pool->buffer, pool->total_bytes);
241#ifdef PROFILING
242 pool->miss_total = pool->miss_bytes = 0;
243 pool->hits_total = pool->hits_bytes = 0;
244 pool->alloc_total = 0;
245#endif
246}
247
258static inline void *sfpool_malloc(void *restrict opaque, const size_t size) {
259 sfpool_t *pool = (sfpool_t*)opaque;
260 void *ptr;
261 if (size <= pool->block_size
262 && pool->free_list != NULL) {
263#ifdef PROFILING
264 pool->hits_total++;
265 pool->hits_bytes+=size;
266 pool->alloc_total+=size;
267#endif
268 // Remove the first block from the free list
269 uint8_t *block = pool->free_list;
270 pool->free_list = *(uint8_t **)block;
271 pool->free_count-- ;
272 return block;
273 }
274 // Fallback to system malloc for large allocations
275 ptr = malloc(size);
276 if(ptr == NULL) perror("system malloc error");
277#ifdef PROFILING
278 pool->miss_total++;
279 pool->miss_bytes+=size;
280 pool->alloc_total+=size;
281#endif
282 return ptr;
283}
284
285
295static inline void sfpool_free(void *restrict opaque, void *ptr) {
296 sfpool_t *pool = (sfpool_t*)opaque;
297 if (ptr == NULL) return; // Freeing NULL is a no-op
298 if (_is_in_pool(pool,ptr)) {
299#ifdef SECURE_ZERO
300 // Zero the user-visible contents before restoring the free-list link.
301 sfutil_zero(ptr, pool->block_size);
302#endif
303 // Add the block back to the free list
304 *(uint8_t **)ptr = pool->free_list;
305 pool->free_list = (uint8_t *)ptr;
306 pool->free_count++ ;
307 return;
308 } else {
309 free(ptr);
310 }
311}
312
313
326static inline void *sfpool_realloc(void *restrict opaque, void *ptr, const size_t size) {
327 sfpool_t *pool = (sfpool_t*)opaque;
328 if (ptr == NULL) {
329 return sfpool_malloc(pool, size);
330 }
331 if (size == 0) {
332 sfpool_free(pool, ptr);
333 return NULL;
334 }
335 if (_is_in_pool((sfpool_t*)pool,ptr)) {
336 if (size <= pool->block_size) {
337#ifdef PROFILING
338 pool->hits_total++;
339 pool->hits_bytes+=size;
340 pool->alloc_total+=size;
341#endif
342 return ptr; // No need to reallocate
343 } else {
344 void *new_ptr = malloc(size);
345 if (new_ptr == NULL) return NULL;
346 memcpy(new_ptr, ptr, pool->block_size); // Copy only BLOCK_SIZE bytes
347#ifdef SECURE_ZERO
348 // Zero the old pool block before relinking it into the free list.
349 sfutil_zero(ptr, pool->block_size);
350#endif
351 // Add the block back to the free list
352 *(uint8_t **)ptr = pool->free_list;
353 pool->free_list = (uint8_t *)ptr;
354 pool->free_count++ ;
355#ifdef PROFILING
356 pool->miss_total++;
357 pool->miss_bytes+=size;
358 pool->alloc_total+=size;
359#endif
360 return new_ptr;
361 }
362 } else {
363 // Handle large allocations
364 return realloc(ptr, size);
365#ifdef PROFILING
366 pool->miss_total++;
367 pool->miss_bytes+=size;
368 pool->alloc_total+=size;
369#endif
370 }
371}
372
382static inline int sfpool_contains(void *restrict opaque, const void *ptr) {
383 sfpool_t *pool = (sfpool_t*)opaque;
384 int res = 0;
385 if( _is_in_pool(pool,ptr) ) res = 1;
386 return res;
387}
388
389
398static inline void sfpool_status(sfpool_t *restrict p) {
399 fprintf(stderr,"\n🌊 sfpool: %u blocks %u B each\n",
400 p->total_blocks, p->block_size);
401#ifdef PROFILING
402 fprintf(stderr,"🌊 Total: %lu K\n",
403 p->alloc_total/1024);
404 fprintf(stderr,"🌊 Misses: %lu K (%u calls)\n",p->miss_bytes/1024,p->miss_total);
405 fprintf(stderr,"🌊 Hits: %lu K (%u calls)\n",p->hits_bytes/1024,p->hits_total);
406#endif
407}
408
// End of sfpool group
410
411#endif