| 1 | /** |
| 2 | * @file allocator.h |
| 3 | * |
| 4 | * Memory management interface for libghostty-vt. |
| 5 | */ |
| 6 | |
| 7 | #ifndef GHOSTTY_VT_ALLOCATOR_H |
| 8 | #define GHOSTTY_VT_ALLOCATOR_H |
| 9 | |
| 10 | #include <stdbool.h> |
| 11 | #include <stddef.h> |
| 12 | #include <stdint.h> |
| 13 | #include <ghostty/vt/types.h> |
| 14 | |
| 15 | /** @defgroup allocator Memory Management |
| 16 | * |
| 17 | * libghostty-vt does require memory allocation for various operations, |
| 18 | * but is resilient to allocation failures and will gracefully handle |
| 19 | * out-of-memory situations by returning error codes. |
| 20 | * |
| 21 | * The exact memory management semantics are documented in the relevant |
| 22 | * functions and data structures. |
| 23 | * |
| 24 | * libghostty-vt uses explicit memory allocation via an allocator |
| 25 | * interface provided by GhosttyAllocator. The interface is based on the |
| 26 | * [Zig](https://ziglang.org) allocator interface, since this has been |
| 27 | * shown to be a flexible and powerful interface in practice and enables |
| 28 | * a wide variety of allocation strategies. |
| 29 | * |
| 30 | * **For the common case, you can pass NULL as the allocator for any |
| 31 | * function that accepts one,** and libghostty will use a default allocator. |
| 32 | * The default allocator will be libc malloc/free if libc is linked. |
| 33 | * Otherwise, a custom allocator is used (currently Zig's SMP allocator) |
| 34 | * that doesn't require any external dependencies. |
| 35 | * |
| 36 | * ## Basic Usage |
| 37 | * |
| 38 | * For simple use cases, you can ignore this interface entirely by passing NULL |
| 39 | * as the allocator parameter to functions that accept one. This will use the |
| 40 | * default allocator (typically libc malloc/free, if libc is linked, but |
| 41 | * we provide our own default allocator if libc isn't linked). |
| 42 | * |
| 43 | * To use a custom allocator: |
| 44 | * 1. Implement the GhosttyAllocatorVtable function pointers |
| 45 | * 2. Create a GhosttyAllocator struct with your vtable and context |
| 46 | * 3. Pass the allocator to functions that accept one |
| 47 | * |
| 48 | * ## Alloc/Free Helpers |
| 49 | * |
| 50 | * ghostty_alloc() and ghostty_free() provide a simple malloc/free-style |
| 51 | * interface for allocating and freeing byte buffers through the library's |
| 52 | * allocator. These are useful when: |
| 53 | * |
| 54 | * - You need to allocate a buffer to pass into a libghostty-vt function |
| 55 | * (e.g. preparing input data for ghostty_terminal_vt_write()). |
| 56 | * - You need to free a buffer returned by a libghostty-vt function |
| 57 | * (e.g. the output of ghostty_formatter_format_alloc()). |
| 58 | * - You are on a platform where the library's internal allocator differs |
| 59 | * from the consumer's C runtime (e.g. Windows, where Zig's libc and |
| 60 | * MSVC's CRT maintain separate heaps), so calling the standard C |
| 61 | * free() on library-allocated memory would be undefined behavior. |
| 62 | * |
| 63 | * Always use the same allocator (or NULL) for both the allocation and |
| 64 | * the corresponding free. |
| 65 | * |
| 66 | * @{ |
| 67 | */ |
| 68 | |
| 69 | /** |
| 70 | * Function table for custom memory allocator operations. |
| 71 | * |
| 72 | * This vtable defines the interface for a custom memory allocator. All |
| 73 | * function pointers must be valid and non-NULL. |
| 74 | * |
| 75 | * @ingroup allocator |
| 76 | * |
| 77 | * If you're not going to use a custom allocator, you can ignore all of |
| 78 | * this. All functions that take an allocator pointer allow NULL to use a |
| 79 | * default allocator. |
| 80 | * |
| 81 | * The interface is based on the Zig allocator interface. I'll say up front |
| 82 | * that it is easy to look at this interface and think "wow, this is really |
| 83 | * overcomplicated". The reason for this complexity is well thought out by |
| 84 | * the Zig folks, and it enables a diverse set of allocation strategies |
| 85 | * as shown by the Zig ecosystem. As a consolation, please note that many |
| 86 | * of the arguments are only needed for advanced use cases and can be |
| 87 | * safely ignored in simple implementations. For example, if you look at |
| 88 | * the Zig implementation of the libc allocator in `lib/std/heap.zig` |
| 89 | * (search for CAllocator), you'll see it is very simple. |
| 90 | * |
| 91 | * We chose to align with the Zig allocator interface because: |
| 92 | * |
| 93 | * 1. It is a proven interface that serves a wide variety of use cases |
| 94 | * in the real world via the Zig ecosystem. It's shown to work. |
| 95 | * |
| 96 | * 2. Our core implementation itself is Zig, and this lets us very |
| 97 | * cheaply and easily convert between C and Zig allocators. |
| 98 | * |
| 99 | * NOTE(mitchellh): In the future, we can have default implementations of |
| 100 | * resize/remap and allow those to be null. |
| 101 | */ |
| 102 | typedef struct { |
| 103 | /** |
| 104 | * Return a pointer to `len` bytes with specified `alignment`, or return |
| 105 | * `NULL` indicating the allocation failed. |
| 106 | * |
| 107 | * @param ctx The allocator context |
| 108 | * @param len Number of bytes to allocate |
| 109 | * @param alignment Required alignment for the allocation. Guaranteed to |
| 110 | * be a power of two between 1 and 16 inclusive. |
| 111 | * @param ret_addr First return address of the allocation call stack (0 if not provided) |
| 112 | * @return Pointer to allocated memory, or NULL if allocation failed |
| 113 | */ |
| 114 | void* (*alloc)(void *ctx, size_t len, uint8_t alignment, uintptr_t ret_addr); |
| 115 | |
| 116 | /** |
| 117 | * Attempt to expand or shrink memory in place. |
| 118 | * |
| 119 | * `memory_len` must equal the length requested from the most recent |
| 120 | * successful call to `alloc`, `resize`, or `remap`. `alignment` must |
| 121 | * equal the same value that was passed as the `alignment` parameter to |
| 122 | * the original `alloc` call. |
| 123 | * |
| 124 | * `new_len` must be greater than zero. |
| 125 | * |
| 126 | * @param ctx The allocator context |
| 127 | * @param memory Pointer to the memory block to resize |
| 128 | * @param memory_len Current size of the memory block |
| 129 | * @param alignment Alignment (must match original allocation) |
| 130 | * @param new_len New requested size |
| 131 | * @param ret_addr First return address of the allocation call stack (0 if not provided) |
| 132 | * @return true if resize was successful in-place, false if relocation would be required |
| 133 | */ |
| 134 | bool (*resize)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr); |
| 135 | |
| 136 | /** |
| 137 | * Attempt to expand or shrink memory, allowing relocation. |
| 138 | * |
| 139 | * `memory_len` must equal the length requested from the most recent |
| 140 | * successful call to `alloc`, `resize`, or `remap`. `alignment` must |
| 141 | * equal the same value that was passed as the `alignment` parameter to |
| 142 | * the original `alloc` call. |
| 143 | * |
| 144 | * A non-`NULL` return value indicates the resize was successful. The |
| 145 | * allocation may have same address, or may have been relocated. In either |
| 146 | * case, the allocation now has size of `new_len`. A `NULL` return value |
| 147 | * indicates that the resize would be equivalent to allocating new memory, |
| 148 | * copying the bytes from the old memory, and then freeing the old memory. |
| 149 | * In such case, it is more efficient for the caller to perform the copy. |
| 150 | * |
| 151 | * `new_len` must be greater than zero. |
| 152 | * |
| 153 | * @param ctx The allocator context |
| 154 | * @param memory Pointer to the memory block to remap |
| 155 | * @param memory_len Current size of the memory block |
| 156 | * @param alignment Alignment (must match original allocation) |
| 157 | * @param new_len New requested size |
| 158 | * @param ret_addr First return address of the allocation call stack (0 if not provided) |
| 159 | * @return Pointer to resized memory (may be relocated), or NULL if manual copy is needed |
| 160 | */ |
| 161 | void* (*remap)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, size_t new_len, uintptr_t ret_addr); |
| 162 | |
| 163 | /** |
| 164 | * Free and invalidate a region of memory. |
| 165 | * |
| 166 | * `memory_len` must equal the length requested from the most recent |
| 167 | * successful call to `alloc`, `resize`, or `remap`. `alignment` must |
| 168 | * equal the same value that was passed as the `alignment` parameter to |
| 169 | * the original `alloc` call. |
| 170 | * |
| 171 | * @param ctx The allocator context |
| 172 | * @param memory Pointer to the memory block to free |
| 173 | * @param memory_len Size of the memory block |
| 174 | * @param alignment Alignment (must match original allocation) |
| 175 | * @param ret_addr First return address of the allocation call stack (0 if not provided) |
| 176 | */ |
| 177 | void (*free)(void *ctx, void *memory, size_t memory_len, uint8_t alignment, uintptr_t ret_addr); |
| 178 | } GhosttyAllocatorVtable; |
| 179 | |
| 180 | /** |
| 181 | * Custom memory allocator. |
| 182 | * |
| 183 | * For functions that take an allocator pointer, a NULL pointer indicates |
| 184 | * that the default allocator should be used. The default allocator will |
| 185 | * be libc malloc/free if we're linking to libc. If libc isn't linked, |
| 186 | * a custom allocator is used (currently Zig's SMP allocator). |
| 187 | * |
| 188 | * @ingroup allocator |
| 189 | * |
| 190 | * Usage example: |
| 191 | * @code |
| 192 | * GhosttyAllocator allocator = { |
| 193 | * .vtable = &my_allocator_vtable, |
| 194 | * .ctx = my_allocator_state |
| 195 | * }; |
| 196 | * @endcode |
| 197 | */ |
| 198 | typedef struct GhosttyAllocator { |
| 199 | /** |
| 200 | * Opaque context pointer passed to all vtable functions. |
| 201 | * This allows the allocator implementation to maintain state |
| 202 | * or reference external resources needed for memory management. |
| 203 | */ |
| 204 | void *ctx; |
| 205 | |
| 206 | /** |
| 207 | * Pointer to the allocator's vtable containing function pointers |
| 208 | * for memory operations (alloc, resize, remap, free). |
| 209 | */ |
| 210 | const GhosttyAllocatorVtable *vtable; |
| 211 | } GhosttyAllocator; |
| 212 | |
| 213 | /** |
| 214 | * Allocate a buffer of `len` bytes. |
| 215 | * |
| 216 | * Uses the provided allocator, or the default allocator if NULL is passed. |
| 217 | * The returned buffer must be freed with ghostty_free() using the same |
| 218 | * allocator. |
| 219 | * |
| 220 | * @param allocator Pointer to the allocator to use, or NULL for the default |
| 221 | * @param len Number of bytes to allocate |
| 222 | * @return Pointer to the allocated buffer, or NULL if allocation failed |
| 223 | * |
| 224 | * @ingroup allocator |
| 225 | */ |
| 226 | GHOSTTY_API uint8_t* ghostty_alloc(const GhosttyAllocator* allocator, size_t len); |
| 227 | |
| 228 | /** |
| 229 | * Free memory that was allocated by a libghostty-vt function. |
| 230 | * |
| 231 | * Use this to free buffers returned by functions such as |
| 232 | * ghostty_formatter_format_alloc(). Pass the same allocator that was |
| 233 | * used for the allocation, or NULL if the default allocator was used. |
| 234 | * |
| 235 | * On platforms where the library's internal allocator differs from the |
| 236 | * consumer's C runtime (e.g. Windows, where Zig's libc and MSVC's CRT |
| 237 | * maintain separate heaps), calling the standard C free() on memory |
| 238 | * allocated by the library causes undefined behavior. This function |
| 239 | * guarantees the correct allocator is used regardless of platform. |
| 240 | * |
| 241 | * It is safe to pass a NULL pointer; the call is a no-op in that case. |
| 242 | * |
| 243 | * @param allocator Pointer to the allocator that was used to allocate the |
| 244 | * memory, or NULL if the default allocator was used |
| 245 | * @param ptr Pointer to the memory to free (may be NULL) |
| 246 | * @param len Length of the allocation in bytes (must match the original |
| 247 | * allocation size) |
| 248 | * |
| 249 | * @ingroup allocator |
| 250 | */ |
| 251 | GHOSTTY_API void ghostty_free(const GhosttyAllocator* allocator, uint8_t* ptr, size_t len); |
| 252 | |
| 253 | /** @} */ |
| 254 | |
| 255 | #endif /* GHOSTTY_VT_ALLOCATOR_H */ |