# Memory Allocator

```c
#define FIO_MEMORY_NAME fio
#include "fio-stl.h"
```

A concurrent, per-arena memory allocator that groups objects with similar lifetimes. It tries to keep related allocations in the same chunk so cleanup is cheap and locality is decent. Define `FIO_MEMORY_DISABLE` to route everything to the system `malloc`/`free` instead.

The prefix `fio` comes from `FIO_MEMORY_NAME`. All public function names are built with `FIO_NAME(FIO_MEMORY_NAME, ...)`, so a custom prefix produces `my_malloc`, `my_free`, and so on.

The module also sets the temporary allocator macros (`FIO_MEM_REALLOC_`, `FIO_MEM_FREE_`, `FIO_MEM_REALLOC_IS_SAFE_`, `FIO_MEM_ALIGNMENT_SIZE_`) for any facil.io type defined in the same `include` scope.

**Note:** `FIO_MALLOC` is a shortcut that defines this allocator with prefix `fio` and general-purpose tuning.

---

## Core API

#### `fio_malloc`

```c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size);
```

Allocates `size` bytes aligned to `FIO_MEMORY_ALIGN_SIZE`. Memory is zeroed if `FIO_MEMORY_INITIALIZE_ALLOCATIONS` is enabled. Allocations above the arena limit use big-block mode or `mmap`.

**Parameters:**
- `size` — bytes to allocate.

**Returns:** pointer to allocated memory, or `NULL` on failure.

#### `fio_calloc`

```c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       calloc)(size_t size_per_unit,
                                               size_t unit_count);
```

Equivalent to `fio_malloc(size_per_unit * unit_count)`.

#### `fio_realloc`

```c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr,
                                                             size_t new_size);
```

Reallocates `ptr` to `new_size`. Copy-free expansion is only attempted for large allocations. Prefer `fio_realloc2` for predictable zeroing.

**Parameters:**
- `ptr` — existing allocation, or `NULL`.
- `new_size` — requested new size.

**Returns:** pointer to the new block, or `NULL` on failure.

#### `fio_realloc2`

```c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr,
                                                              size_t new_size,
                                                              size_t copy_len);
```

Reallocates `ptr` to `new_size`, copying at most `copy_len` bytes. Memory beyond `copy_len` is zeroed when `FIO_MEMORY_INITIALIZE_ALLOCATIONS` is enabled.

**Parameters:**
- `ptr` — existing allocation, or `NULL`.
- `new_size` — requested new size.
- `copy_len` — maximum bytes to copy from the old block.

**Returns:** pointer to the new block, or `NULL` on failure.

#### `fio_free`

```c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr);
```

Frees memory allocated by this allocator. Freeing a pointer from a different allocator, or after cleanup, is undefined behavior.

#### `fio_mmap`

```c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size);
```

Allocates directly from the system with `mmap` semantics. Use this for large, long-lived objects. The allocation is slower but avoids arena bookkeeping.

**Parameters:**
- `size` — bytes to allocate.

**Returns:** pointer to allocated memory, or `NULL` on failure.

#### `fio_malloc_after_fork`

```c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void);
```

Reinitializes allocator locks after `fork` in the child process. Best effort only: forking a multi-threaded process is unsafe. Prefer calling `fio_state_callback_force(FIO_CALL_IN_CHILD);` to reset all registered allocators.

#### `fio_realloc_is_safe`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, realloc_is_safe)(void);
```

Returns non-zero if the allocator zeroes allocations, which makes `fio_realloc2` safe for the uncopied tail.

---

## Aligned Allocation API

The aligned API requests a minimum pointer alignment. `fio_malloc_aligned` and `fio_calloc_aligned` are thin wrappers around `fio_realloc_aligned`, which is the single entry point.

#### `fio_realloc_aligned`

```c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME,
                                   realloc_aligned)(void *ptr,
                                                    size_t new_size,
                                                    size_t copy_len,
                                                    size_t alignment);
```

Reallocates `ptr` to `new_size` (copying at most `copy_len` bytes, like `fio_realloc2`), guaranteeing the returned pointer is aligned to at least `alignment`.

The `alignment` argument is normalized:

- `0` selects the allocator default (`FIO_MEMORY_ALIGN_SIZE`).
- Non power-of-2 values are rounded **down** to the nearest power of 2.
- The effective alignment is the maximum of the (rounded) request, the current alignment of `ptr`, and `FIO_MEMORY_ALIGN_SIZE` — alignment never regresses across a realloc.
- Values above `FIO_MEMORY_SYS_ALLOCATION_SIZE` fail: returns `NULL` and sets `errno` to `EINVAL` (debug builds also trip `FIO_ASSERT_DEBUG`).

In-place growth only occurs when the existing pointer already satisfies the requested alignment; otherwise the data is copied to a newly aligned block.

`realloc_aligned(NULL, size, 0, alignment)` allocates (like `malloc_aligned`), and `realloc_aligned(ptr, 0, copy_len, alignment)` frees `ptr`.

#### `fio_malloc_aligned`

```c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       malloc_aligned)(size_t size,
                                                       size_t alignment);
```

Equivalent to `fio_realloc_aligned(NULL, size, 0, alignment)`. A zero `size` returns the shared zero-allocation pointer (aligned only to `FIO_MEMORY_ALIGN_SIZE`), which may be freed or realloc'd normally.

#### `fio_calloc_aligned`

```c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       calloc_aligned)(size_t size_per_unit,
                                                       size_t unit_count,
                                                       size_t alignment);
```

Same as `fio_malloc_aligned(size_per_unit * unit_count, alignment)`, but zeroes the (rounded) allocation. Returns `NULL` on size overflow.

#### `fio_alloc_size`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, alloc_size)(size_t minimum_bytes);
```

Returns the number of usable bytes the allocator actually reserves for a request of `minimum_bytes` (the size class): rounded up to `FIO_MEMORY_ALIGN_SIZE` for arena/big-block allocations, and to whole pages (minus the header offset) for page-backed ones. Alignment padding is reserved in addition to the returned value. With `FIO_MEMORY_DISABLE` the system allocator exposes no rounding guarantee and `minimum_bytes` is returned unchanged.

### Alignment abstraction macros

```c
FIO_MEM_REALLOC_ALIGNED(ptr, old_size, new_size, copy_len, alignment)
FIO_MEM_FREE_ALIGNED(ptr, size)
FIO_MEM_ALLOC_SIZE(size)
```

`FIO_MEM_REALLOC_ALIGNED` mirrors `FIO_MEM_REALLOC` with an added `alignment` argument and **assigns** the result to `ptr` (test with `FIO_ASSERT_ALLOC(ptr)` after the call, restoring a saved copy on failure). `FIO_MEM_FREE_ALIGNED` releases memory obtained through the aligned API, and `FIO_MEM_ALLOC_SIZE` mirrors `fio_alloc_size`.

The macros route to the active allocator, exactly like `FIO_MEM_REALLOC` does: the fio allocator when it was included (`H___FIO_MALLOC___H`), or portable system-allocator fallbacks otherwise. Template-local variants (`FIO_MEM_REALLOC_ALIGNED_`, `FIO_MEM_FREE_ALIGNED_`) exist for `FIO_MEMORY_NAME` template scope, mirroring `FIO_MEM_REALLOC_` / `FIO_MEM_FREE_`.

**Pairing rule:** with the fio allocator, `free` accepts any pointer returned by the aligned API (interior pointers included), so `FIO_MEM_FREE_ALIGNED` is identical to `FIO_MEM_FREE`. Under `FIO_MEMORY_DISABLE` on Windows the aligned API uses `_aligned_malloc` / `_aligned_realloc`, whose memory **must** be released with `FIO_MEM_FREE_ALIGNED` (`_aligned_free`) — plain `free` is heap corruption. On POSIX both map to `free`. Custom allocators that stash alignment bookkeeping near the returned pointer should hook the macro pair, not the fio functions.

### Alignment guarantees per configuration

| Configuration | Default alignment | Aligned API backend | Max alignment |
|---|---|---|---|
| fio allocator (any OS) | `FIO_MEMORY_ALIGN_SIZE` (64) | interior-pointer slice / big-block / page-backed | `FIO_MEMORY_SYS_ALLOCATION_SIZE` |
| `FIO_MEMORY_DISABLE`, POSIX | `_Alignof(max_align_t)` | `posix_memalign` + `free` | `FIO_MEMORY_SYS_ALLOCATION_SIZE` |
| `FIO_MEMORY_DISABLE`, Windows | `_Alignof(max_align_t)` (16) | `_aligned_malloc` / `_aligned_realloc` / `_aligned_free` | `FIO_MEMORY_SYS_ALLOCATION_SIZE` |
| `FIO_MEMORY_DISABLE`, other | `_Alignof(max_align_t)` | over-allocation with stashed raw pointer | `FIO_MEMORY_SYS_ALLOCATION_SIZE` |

When `FIO_MALLOC_OVERRIDE_SYSTEM` is defined, the libc aligned-allocation family is overridden as well (`aligned_alloc`, `posix_memalign`, and on Windows `_aligned_malloc` / `_aligned_realloc` / `_aligned_free`), so libc-aligned pointers never reach the fio allocator (and vice versa).

---

## Configuration Macros

#### `FIO_MEMORY_NAME`

```c
#define FIO_MEMORY_NAME fio
```

Required prefix for the allocator's public symbols. Must be defined before including the header.

#### `FIO_MALLOC`

```c
#define FIO_MALLOC
```

Shortcut that defines the allocator with prefix `fio` and tunes it for general use. It also redefines `FIO_MEM_REALLOC`, `FIO_MEM_FREE`, `FIO_MEM_REALLOC_IS_SAFE`, and `FIO_MEM_ALIGNMENT_SIZE`.

#### `FIO_MEMORY_DISABLE`

```c
#define FIO_MEMORY_DISABLE
```

Bypasses the custom allocator and routes all allocations to the system `malloc`/`free`.

#### `FIO_MEMORY_ALIGN_LOG`

```c
#define FIO_MEMORY_ALIGN_LOG 6
```

Log2 of the allocation alignment. Clamped to the range `3`–`10` (8 to 1024 bytes). Default is `6` (64 bytes).

#### `FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG`

```c
#define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 21
```

Log2 of the chunk size allocated from the system. Clamped to `17`–`24`. Default is `21` (~2 MB).

#### `FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG`

```c
#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 2
```

Log2 of the number of blocks per system chunk. Range `0`–`4`. Affects fragmentation and the threshold for big allocations.

#### `FIO_MEMORY_ENABLE_BIG_ALLOC`

```c
#define FIO_MEMORY_ENABLE_BIG_ALLOC 1
```

When enabled, large allocations can consume an entire chunk as a single big block. May increase fragmentation for long-lived objects.

#### `FIO_MEMORY_ARENA_COUNT`

```c
#define FIO_MEMORY_ARENA_COUNT -1
```

Number of arenas. Negative or zero means dynamic selection based on CPU core count, capped by `FIO_MEMORY_ARENA_COUNT_MAX` and fallback `FIO_MEMORY_ARENA_COUNT_FALLBACK`.

#### `FIO_MEMORY_ARENA_COUNT_FALLBACK`

```c
#define FIO_MEMORY_ARENA_COUNT_FALLBACK 24
```

Default arena count when dynamic CPU detection fails.

#### `FIO_MEMORY_ARENA_COUNT_MAX`

```c
#define FIO_MEMORY_ARENA_COUNT_MAX 64
```

Upper bound for dynamic arena count.

#### `FIO_MEMORY_USE_THREAD_MUTEX`

```c
#define FIO_MEMORY_USE_THREAD_MUTEX 0
```

When `1`, arenas protect themselves with `pthread` mutexes instead of spinlocks. Default is `1` when `FIO_MEMORY_ARENA_COUNT` is positive.

#### `FIO_MEMORY_INITIALIZE_ALLOCATIONS`

```c
#define FIO_MEMORY_INITIALIZE_ALLOCATIONS \
        FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT
```

When `1`, allocations return zeroed memory and `realloc2` zeroes uncopied bytes. Default is `1`.

#### `FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT`

```c
#define FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT 1
```

Default value for `FIO_MEMORY_INITIALIZE_ALLOCATIONS`.

#### `FIO_MEMORY_CACHE_SLOTS`

```c
#define FIO_MEMORY_CACHE_SLOTS 4
```

Number of freed chunks to keep cached before returning memory to the system.

#### `FIO_MEMORY_WARMUP`

```c
#define FIO_MEMORY_WARMUP 0
```

If set to a positive number, that many arenas pre-allocate a block at startup. Usually best left at `0`.

#### `FIO_MEM_SYS_ALLOC`, `FIO_MEM_SYS_REALLOC`, `FIO_MEM_SYS_FREE`

```c
#define FIO_MEM_SYS_ALLOC(pages, alignment_log) /* ... */
#define FIO_MEM_SYS_REALLOC(ptr, old_pages, new_pages, alignment_log) /* ... */
#define FIO_MEM_SYS_FREE(ptr, pages) /* ... */
```

Override all three to replace the system allocation backend. Alignment is essential and may be large.

---

## Introspection

These functions are exposed for debugging. Treat them as unstable.

#### `fio_malloc_arenas`

```c
SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arenas)(void);
```

Returns the number of arenas.

#### `fio_malloc_block_size`

```c
SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void);
```

Returns the block size used for arena allocations.

#### `fio_malloc_sys_alloc_size`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_sys_alloc_size)(void);
```

Returns the system chunk size in bytes.

#### `fio_malloc_cache_slots`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_cache_slots)(void);
```

Returns the configured number of cache slots.

#### `fio_malloc_alignment`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment)(void);
```

Returns the allocation alignment in bytes.

#### `fio_malloc_alignment_log`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment_log)(void);
```

Returns log2 of the allocation alignment.

#### `fio_malloc_alloc_limit`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alloc_limit)(void);
```

Returns the size above which allocations bypass arena blocks.

#### `fio_malloc_arena_alloc_limit`

```c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arena_alloc_limit)(void);
```

Returns the arena block size limit.

#### `fio_malloc_print_state`

```c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void);
```

Prints allocator state to `stderr`.

#### `fio_malloc_print_free_block_list`

```c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void);
```

Prints the free-block list to `stderr`.

#### `fio_malloc_print_settings`

```c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void);
```

Prints allocator settings to `stderr`.

---

## Example

```c
#define FIO_MALLOC
#include "fio-stl.h"

int main(void) {
  char *buf = fio_malloc(256);
  if (!buf) return -1;

  fio_memcpy(buf, "hello", 5);
  fprintf(stderr, "%s\n", buf);

  char *buf2 = fio_realloc2(buf, 512, 5);
  if (!buf2) {
    fio_free(buf);
    return -1;
  }
  buf = buf2;

  fio_free(buf);
  return 0;
}
```

------------------------------------------------------------
