Files
neovim/src/nvim/memory.c
bfredl e656d7be2e perf(tui): faster implementation of terminfo
The processing of terminfo can be separated into two steps:

1. The initialization of terminfo, which includes trying to find $TERM
   in a terminfo database file. As a fallback, common terminfo
   definitions are compiled in. After this, we apply a lot of ad-hoc
   patching to cover over limitations of terminfo.

2. While processing updates from nvim, actually using terminfo strings
   and formatting them with runtime values. for this part, terminfo
   essentially is a hyper-enhanced version of snprintf(), including
   a sm0l stack based virtual machine which can manipulate the runtime
   parameters.

This PR completely replaces libuniblium for step 2, with code
vendored from NetBSD's libtermkey which has been adapted to use typesafe
input parameters and to write into an output buffer in place.

The most immedatiate effects is a performance enhancement of
update_attrs() which is a very hot function when profiling the
TUI-process part of screen updates. In a stupid microbenchmark
(essentially calling nvim__screenshot over and over in a loop) this
leads to a speedup of ca 1.5x for redrawing the screen on the TUI-side.
What this means in practise when using nvim as a text editor is probably
no noticible effect at all, and when reabusing nvim as idk a full screen
RGB ASCII art rendrer maybe an increase from 72 to 75 FPS LMAO.

As nice side-effect, reduce the usage of unibilium to initialization only..
which will make it easier to remove, replace or make unibilium optional,
adressing #31989. Specifically, the builtin fallback doesn't use
unibilium at all, so a unibilium-free build is in principle possible
if the builtin definitions are good enough.

As a caveat, this PR doesn't touch libtermkey at all, which still has a
conditional dependency on unibilium. This will be investigated in a
follow-up PR

Note: the check of $TERMCOLOR was moved from tui/tui.c to
_defaults.lua in d7651b27d5 as we want to
skip the logic in _defaults.lua if the env var was set, but there
is no harm in TUI getting the right value when the TUI is trying to
initialize its terminfo shenanigans. Also this check is needed when
a TUI connects to a `--headless` server later, which will observe
a different $TERMCOLOR value than the nvim core process itself.
2025-10-18 11:36:16 +02:00

1017 lines
27 KiB
C

// Various routines dealing with allocation and deallocation of memory.
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "nvim/api/extmark.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/arglist.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/channel.h"
#include "nvim/context.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawline.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/map_defs.h"
#include "nvim/mapping.h"
#include "nvim/memfile.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_vars.h"
#include "nvim/sign.h"
#include "nvim/state_defs.h"
#include "nvim/statusline.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
#include "nvim/ui_compositor.h"
#include "nvim/usercmd.h"
#ifdef UNIT_TESTING
# define malloc(size) mem_malloc(size)
# define calloc(count, size) mem_calloc(count, size)
# define realloc(ptr, size) mem_realloc(ptr, size)
# define free(ptr) mem_free(ptr)
MemMalloc mem_malloc = &malloc;
MemFree mem_free = &free;
MemCalloc mem_calloc = &calloc;
MemRealloc mem_realloc = &realloc;
#endif
#include "memory.c.generated.h"
#ifdef EXITFREE
bool entered_free_all_mem = false;
#endif
/// Try to free memory. Used when trying to recover from out of memory errors.
/// @see {xmalloc}
static void try_to_free_memory(void)
{
static bool trying_to_free = false;
// avoid recursive calls
if (trying_to_free) {
return;
}
trying_to_free = true;
// free any scrollback text
clear_sb_text(true);
// Try to save all buffers and release as many blocks as possible
mf_release_all();
arena_free_reuse_blks();
trying_to_free = false;
}
/// Avoid repeating the error message many times (they take 1 second each).
/// `did_outofmem_msg` is reset when a character is read.
static void do_outofmem_msg(size_t size)
{
if (did_outofmem_msg) {
return;
}
// Don't hide this message
emsg_silent = 0;
// Must come first to avoid coming back here when printing the error
// message fails, e.g. when setting v:errmsg.
did_outofmem_msg = true;
semsg(_("E342: Out of memory! (allocating %" PRIu64 " bytes)"), (uint64_t)size);
}
/// malloc() wrapper
///
/// try_malloc() is a malloc() wrapper that tries to free some memory before
/// trying again.
///
/// @see {try_to_free_memory}
/// @param size
/// @return pointer to allocated space. NULL if out of memory
void *try_malloc(size_t size) FUNC_ATTR_MALLOC FUNC_ATTR_ALLOC_SIZE(1)
{
size_t allocated_size = size ? size : 1;
void *ret = malloc(allocated_size);
if (!ret) {
try_to_free_memory();
ret = malloc(allocated_size);
}
return ret;
}
/// try_malloc() wrapper that shows an out-of-memory error message to the user
/// before returning NULL
///
/// @see {try_malloc}
/// @param size
/// @return pointer to allocated space. NULL if out of memory
void *verbose_try_malloc(size_t size) FUNC_ATTR_MALLOC FUNC_ATTR_ALLOC_SIZE(1)
{
void *ret = try_malloc(size);
if (!ret) {
do_outofmem_msg(size);
}
return ret;
}
/// malloc() wrapper that never returns NULL
///
/// xmalloc() succeeds or gracefully aborts when out of memory.
/// Before aborting try to free some memory and call malloc again.
///
/// @see {try_to_free_memory}
/// @param size
/// @return pointer to allocated space. Never NULL
void *xmalloc(size_t size)
FUNC_ATTR_MALLOC FUNC_ATTR_ALLOC_SIZE(1) FUNC_ATTR_NONNULL_RET
{
void *ret = try_malloc(size);
if (!ret) {
preserve_exit(e_outofmem);
}
return ret;
}
/// free() wrapper that delegates to the backing memory manager
///
/// @note Use XFREE_CLEAR() instead, if possible.
void xfree(void *ptr)
{
free(ptr);
}
/// calloc() wrapper
///
/// @see {xmalloc}
/// @param count
/// @param size
/// @return pointer to allocated space. Never NULL
void *xcalloc(size_t count, size_t size)
FUNC_ATTR_MALLOC FUNC_ATTR_ALLOC_SIZE_PROD(1, 2) FUNC_ATTR_NONNULL_RET
{
size_t allocated_count = count && size ? count : 1;
size_t allocated_size = count && size ? size : 1;
void *ret = calloc(allocated_count, allocated_size);
if (!ret) {
try_to_free_memory();
ret = calloc(allocated_count, allocated_size);
if (!ret) {
preserve_exit(e_outofmem);
}
}
return ret;
}
/// realloc() wrapper
///
/// @see {xmalloc}
/// @param size
/// @return pointer to reallocated space. Never NULL
void *xrealloc(void *ptr, size_t size)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALLOC_SIZE(2) FUNC_ATTR_NONNULL_RET
{
size_t allocated_size = size ? size : 1;
void *ret = realloc(ptr, allocated_size);
if (!ret) {
try_to_free_memory();
ret = realloc(ptr, allocated_size);
if (!ret) {
preserve_exit(e_outofmem);
}
}
return ret;
}
/// xmalloc() wrapper that allocates size + 1 bytes and zeroes the last byte
///
/// Commonly used to allocate strings, e.g. `char *s = xmallocz(len)`.
///
/// @see {xmalloc}
/// @param size
/// @return pointer to allocated space. Never NULL
void *xmallocz(size_t size)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t total_size = size + 1;
if (total_size < size) {
preserve_exit(_("Nvim: Data too large to fit into virtual memory space\n"));
}
void *ret = xmalloc(total_size);
((char *)ret)[size] = NUL;
return ret;
}
/// Allocates (len + 1) bytes of memory, duplicates `len` bytes of
/// `data` to the allocated memory, zero terminates the allocated memory,
/// and returns a pointer to the allocated memory. If the allocation fails,
/// the program dies.
///
/// @see {xmalloc}
/// @param data Pointer to the data that will be copied
/// @param len number of bytes that will be copied
void *xmemdupz(const void *data, size_t len)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ALL
{
return memcpy(xmallocz(len), data, len);
}
/// Copies `len` bytes of `src` to `dst` and zero terminates it.
///
/// @see {xstrlcpy}
/// @param[out] dst Buffer to store the result.
/// @param[in] src Buffer to be copied.
/// @param[in] len Number of bytes to be copied.
void *xmemcpyz(void *dst, const void *src, size_t len)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
memcpy(dst, src, len);
((char *)dst)[len] = NUL;
return dst;
}
#ifndef HAVE_STRNLEN
size_t xstrnlen(const char *s, size_t n)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
const char *end = memchr(s, NUL, n);
if (end == NULL) {
return n;
}
return (size_t)(end - s);
}
#endif
/// A version of strchr() that returns a pointer to the terminating NUL if it
/// doesn't find `c`.
///
/// @param str The string to search.
/// @param c The char to look for.
/// @returns a pointer to the first instance of `c`, or to the NUL terminator
/// if not found.
char *xstrchrnul(const char *str, char c)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
char *p = strchr(str, c);
return p ? p : (char *)(str + strlen(str));
}
/// A version of memchr() that returns a pointer one past the end
/// if it doesn't find `c`.
///
/// @param addr The address of the memory object.
/// @param c The char to look for.
/// @param size The size of the memory object.
/// @returns a pointer to the first instance of `c`, or one past the end if not
/// found.
void *xmemscan(const void *addr, char c, size_t size)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
char *p = memchr(addr, c, size);
return p ? p : (char *)addr + size;
}
/// Replaces every instance of `c` with `x`.
///
/// @warning Will read past `str + strlen(str)` if `c == NUL`.
///
/// @param str A NUL-terminated string.
/// @param c The unwanted byte.
/// @param x The replacement.
void strchrsub(char *str, char c, char x)
FUNC_ATTR_NONNULL_ALL
{
assert(c != NUL);
while ((str = strchr(str, c))) {
*str++ = x;
}
}
/// Replaces every instance of `c` with `x`.
///
/// @param data An object in memory. May contain NULs.
/// @param c The unwanted byte.
/// @param x The replacement.
/// @param len The length of data.
void memchrsub(void *data, char c, char x, size_t len)
FUNC_ATTR_NONNULL_ALL
{
char *p = data;
char *end = (char *)data + len;
while ((p = memchr(p, c, (size_t)(end - p)))) {
*p++ = x;
}
}
/// Counts the number of occurrences of `c` in `str`.
///
/// @warning Unsafe if `c == NUL`.
///
/// @param str Pointer to the string to search.
/// @param c The byte to search for.
/// @returns the number of occurrences of `c` in `str`.
size_t strcnt(const char *str, char c)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
assert(c != 0);
size_t cnt = 0;
while ((str = strchr(str, c))) {
cnt++;
str++; // Skip the instance of c.
}
return cnt;
}
/// Counts the number of occurrences of byte `c` in `data[len]`.
///
/// @param data Pointer to the data to search.
/// @param c The byte to search for.
/// @param len The length of `data`.
/// @returns the number of occurrences of `c` in `data[len]`.
size_t memcnt(const void *data, char c, size_t len)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
size_t cnt = 0;
const char *ptr = data;
const char *end = ptr + len;
while ((ptr = memchr(ptr, c, (size_t)(end - ptr))) != NULL) {
cnt++;
ptr++; // Skip the instance of c.
}
return cnt;
}
/// Copies the string pointed to by src (including the terminating NUL
/// character) into the array pointed to by dst.
///
/// @returns pointer to the terminating NUL char copied into the dst buffer.
/// This is the only difference with strcpy(), which returns dst.
///
/// WARNING: If copying takes place between objects that overlap, the behavior
/// is undefined.
///
/// Nvim version of POSIX 2008 stpcpy(3). We do not require POSIX 2008, so
/// implement our own version.
///
/// @param dst
/// @param src
char *xstpcpy(char *restrict dst, const char *restrict src)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const size_t len = strlen(src);
return (char *)memcpy(dst, src, len + 1) + len;
}
/// Copies not more than n bytes (bytes that follow a NUL character are not
/// copied) from the array pointed to by src to the array pointed to by dst.
///
/// If a NUL character is written to the destination, xstpncpy() returns the
/// address of the first such NUL character. Otherwise, it shall return
/// &dst[maxlen].
///
/// WARNING: If copying takes place between objects that overlap, the behavior
/// is undefined.
///
/// WARNING: xstpncpy will ALWAYS write maxlen bytes. If src is shorter than
/// maxlen, zeroes will be written to the remaining bytes.
///
/// @param dst
/// @param src
/// @param maxlen
char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const char *p = memchr(src, NUL, maxlen);
if (p) {
size_t srclen = (size_t)(p - src);
memcpy(dst, src, srclen);
memset(dst + srclen, 0, maxlen - srclen);
return dst + srclen;
} else {
memcpy(dst, src, maxlen);
return dst + maxlen;
}
}
/// xstrlcpy - Copy a NUL-terminated string into a sized buffer
///
/// Compatible with *BSD strlcpy: the result is always a valid NUL-terminated
/// string that fits in the buffer (unless, of course, the buffer size is
/// zero). It does not pad out the result like strncpy() does.
///
/// @param[out] dst Buffer to store the result.
/// @param[in] src String to be copied.
/// @param[in] dsize Size of `dst`.
///
/// @return Length of `src`. May be greater than `dsize - 1`, which would mean
/// that string was truncated.
size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize)
FUNC_ATTR_NONNULL_ALL
{
size_t slen = strlen(src);
if (dsize) {
size_t len = MIN(slen, dsize - 1);
memcpy(dst, src, len);
dst[len] = NUL;
}
return slen; // Does not include NUL.
}
/// Appends `src` to string `dst` of size `dsize` (unlike strncat, dsize is the
/// full size of `dst`, not space left). At most dsize-1 characters
/// will be copied. Always NUL terminates. `src` and `dst` may overlap.
///
/// @see vim_strcat from Vim.
/// @see strlcat from OpenBSD.
///
/// @param[in,out] dst Buffer to be appended-to. Must have a NUL byte.
/// @param[in] src String to put at the end of `dst`.
/// @param[in] dsize Size of `dst` including NUL byte. Must be greater than 0.
///
/// @return Length of the resulting string as if destination size was #SIZE_MAX.
/// May be greater than `dsize - 1`, which would mean that string was
/// truncated.
size_t xstrlcat(char *const dst, const char *const src, const size_t dsize)
FUNC_ATTR_NONNULL_ALL
{
assert(dsize > 0);
const size_t dlen = strlen(dst);
assert(dlen < dsize);
const size_t slen = strlen(src);
if (slen > dsize - dlen - 1) {
memmove(dst + dlen, src, dsize - dlen - 1);
dst[dsize - 1] = NUL;
} else {
memmove(dst + dlen, src, slen + 1);
}
return slen + dlen; // Does not include NUL.
}
/// strdup() wrapper
///
/// @see {xmalloc}
/// @param str 0-terminated string that will be copied
/// @return pointer to a copy of the string
char *xstrdup(const char *str)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
FUNC_ATTR_NONNULL_ALL
{
return xmemdupz(str, strlen(str));
}
/// strdup() wrapper
///
/// Unlike xstrdup() allocates a new empty string if it receives NULL.
char *xstrdupnul(const char *const str)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
if (str == NULL) {
return xmallocz(0);
}
return xstrdup(str);
}
/// A version of memchr that starts the search at `src + len`.
///
/// Based on glibc's memrchr.
///
/// @param src The source memory object.
/// @param c The byte to search for.
/// @param len The length of the memory object.
/// @returns a pointer to the found byte in src[len], or NULL.
void *xmemrchr(const void *src, uint8_t c, size_t len)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
while (len--) {
if (((uint8_t *)src)[len] == c) {
return (uint8_t *)src + len;
}
}
return NULL;
}
/// strndup() wrapper
///
/// @see {xmalloc}
/// @param str 0-terminated string that will be copied
/// @return pointer to a copy of the string
char *xstrndup(const char *str, size_t len)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
FUNC_ATTR_NONNULL_ALL
{
char *p = memchr(str, NUL, len);
return xmemdupz(str, p ? (size_t)(p - str) : len);
}
/// Duplicates a chunk of memory using xmalloc
///
/// @see {xmalloc}
/// @param data pointer to the chunk
/// @param len size of the chunk
/// @return a pointer
void *xmemdup(const void *data, size_t len)
FUNC_ATTR_MALLOC FUNC_ATTR_ALLOC_SIZE(2) FUNC_ATTR_NONNULL_RET
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return memcpy(xmalloc(len), data, len);
}
/// Returns true if strings `a` and `b` are equal. Arguments may be NULL.
bool strequal(const char *a, const char *b)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return (a == NULL && b == NULL) || (a && b && strcmp(a, b) == 0);
}
/// Returns true if first `n` characters of strings `a` and `b` are equal. Arguments may be NULL.
bool strnequal(const char *a, const char *b, size_t n)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return (a == NULL && b == NULL) || (a && b && strncmp(a, b, n) == 0);
}
/// Writes time_t to "buf[8]".
void time_to_bytes(time_t time_, uint8_t buf[8])
{
// time_t can be up to 8 bytes in size, more than uintmax_t in 32 bits
// systems, thus we can't use put_bytes() here.
for (size_t i = 7, bufi = 0; bufi < 8; i--, bufi++) {
buf[bufi] = (uint8_t)((uint64_t)time_ >> (i * 8));
}
}
/// Iterative merge sort for doubly linked list.
/// O(NlogN) worst case, and stable.
/// - The list is divided into blocks of increasing size (1, 2, 4, 8, ...).
/// - Each pair of blocks is merged in sorted order.
/// - Merged blocks are reconnected to build the sorted list.
void *mergesort_list(void *head, MergeSortGetFunc get_next, MergeSortSetFunc set_next,
MergeSortGetFunc get_prev, MergeSortSetFunc set_prev,
MergeSortCompareFunc compare)
{
if (!head || !get_next(head)) {
return head;
}
// Count length
int n = 0;
void *curr = head;
while (curr) {
n++;
curr = get_next(curr);
}
for (int size = 1; size < n; size *= 2) {
void *new_head = NULL;
void *tail = NULL;
curr = head;
while (curr) {
// Split two runs
void *left = curr;
void *right = left;
for (int i = 0; i < size && right; i++) {
right = get_next(right);
}
void *next = right;
for (int i = 0; i < size && next; i++) {
next = get_next(next);
}
// Break links
void *l_end = right ? get_prev(right) : NULL;
if (l_end) {
set_next(l_end, NULL);
}
if (right) {
set_prev(right, NULL);
}
void *r_end = next ? get_prev(next) : NULL;
if (r_end) {
set_next(r_end, NULL);
}
if (next) {
set_prev(next, NULL);
}
// Merge
void *merged = NULL;
void *merged_tail = NULL;
while (left || right) {
void *chosen = NULL;
if (!left) {
chosen = right;
right = get_next(right);
} else if (!right) {
chosen = left;
left = get_next(left);
} else if (compare(left, right) <= 0) {
chosen = left;
left = get_next(left);
} else {
chosen = right;
right = get_next(right);
}
if (merged_tail) {
set_next(merged_tail, chosen);
set_prev(chosen, merged_tail);
merged_tail = chosen;
} else {
merged = merged_tail = chosen;
set_prev(chosen, NULL);
}
}
// Connect to full list
if (!new_head) {
new_head = merged;
} else {
set_next(tail, merged);
set_prev(merged, tail);
}
// Move tail to end
while (get_next(merged_tail)) {
merged_tail = get_next(merged_tail);
}
tail = merged_tail;
curr = next;
}
head = new_head;
}
return head;
}
#define REUSE_MAX 4
static struct consumed_blk *arena_reuse_blk;
static size_t arena_reuse_blk_count = 0;
static void arena_free_reuse_blks(void)
{
while (arena_reuse_blk_count > 0) {
struct consumed_blk *blk = arena_reuse_blk;
arena_reuse_blk = arena_reuse_blk->prev;
xfree(blk);
arena_reuse_blk_count--;
}
}
/// Finish the allocations in an arena.
///
/// This does not immediately free the memory, but leaves existing allocated
/// objects valid, and returns an opaque ArenaMem handle, which can be used to
/// free the allocations using `arena_mem_free`, when the objects allocated
/// from the arena are not needed anymore.
ArenaMem arena_finish(Arena *arena)
{
struct consumed_blk *res = (struct consumed_blk *)arena->cur_blk;
*arena = (Arena)ARENA_EMPTY;
return res;
}
/// allocate a block of ARENA_BLOCK_SIZE
///
/// free it with free_block
void *alloc_block(void)
{
if (arena_reuse_blk_count > 0) {
void *retval = (char *)arena_reuse_blk;
arena_reuse_blk = arena_reuse_blk->prev;
arena_reuse_blk_count--;
return retval;
} else {
arena_alloc_count++;
return xmalloc(ARENA_BLOCK_SIZE);
}
}
void arena_alloc_block(Arena *arena)
{
struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk;
arena->cur_blk = alloc_block();
arena->pos = 0;
arena->size = ARENA_BLOCK_SIZE;
struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true);
blk->prev = prev_blk;
}
static size_t arena_align_offset(uint64_t off)
{
#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double))
return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1));
#undef ARENA_ALIGN
}
/// @param arena if NULL, do a global allocation. caller must then free the value!
/// @param size if zero, will still return a non-null pointer, but not a usable or unique one
void *arena_alloc(Arena *arena, size_t size, bool align)
{
if (!arena) {
return xmalloc(size);
}
if (!arena->cur_blk) {
arena_alloc_block(arena);
}
size_t alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos;
if (alloc_pos + size > arena->size) {
if (size > (ARENA_BLOCK_SIZE - sizeof(struct consumed_blk)) >> 1) {
// if allocation is too big, allocate a large block with the requested
// size, but still with block pointer head. We do this even for
// arena->size / 2, as there likely is space left for the next
// small allocation in the current block.
arena_alloc_count++;
size_t hdr_size = sizeof(struct consumed_blk);
size_t aligned_hdr_size = (align ? arena_align_offset(hdr_size) : hdr_size);
char *alloc = xmalloc(size + aligned_hdr_size);
// to simplify free-list management, arena->cur_blk must
// always be a normal, ARENA_BLOCK_SIZE sized, block
struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk;
struct consumed_blk *fix_blk = (struct consumed_blk *)alloc;
fix_blk->prev = cur_blk->prev;
cur_blk->prev = fix_blk;
return alloc + aligned_hdr_size;
} else {
arena_alloc_block(arena); // resets arena->pos
alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos;
}
}
char *mem = arena->cur_blk + alloc_pos;
arena->pos = alloc_pos + size;
return mem;
}
void free_block(void *block)
{
if (arena_reuse_blk_count < REUSE_MAX) {
struct consumed_blk *reuse_blk = block;
reuse_blk->prev = arena_reuse_blk;
arena_reuse_blk = reuse_blk;
arena_reuse_blk_count++;
} else {
xfree(block);
}
}
void arena_mem_free(ArenaMem mem)
{
struct consumed_blk *b = mem;
// peel of the first block, as it is guaranteed to be ARENA_BLOCK_SIZE,
// not a custom fix_blk
if (b != NULL) {
struct consumed_blk *reuse_blk = b;
b = b->prev;
free_block(reuse_blk);
}
while (b) {
struct consumed_blk *prev = b->prev;
xfree(b);
b = prev;
}
}
char *arena_allocz(Arena *arena, size_t size)
{
char *mem = arena_alloc(arena, size + 1, false);
mem[size] = NUL;
return mem;
}
char *arena_memdupz(Arena *arena, const char *buf, size_t size)
FUNC_ATTR_NONNULL_ARG(2)
{
char *mem = arena_allocz(arena, size);
memcpy(mem, buf, size);
return mem;
}
char *arena_strdup(Arena *arena, const char *str)
FUNC_ATTR_NONNULL_ARG(2)
{
return arena_memdupz(arena, str, strlen(str));
}
#if defined(EXITFREE)
# include "nvim/autocmd.h"
# include "nvim/buffer.h"
# include "nvim/cmdhist.h"
# include "nvim/diff.h"
# include "nvim/edit.h"
# include "nvim/ex_cmds.h"
# include "nvim/ex_docmd.h"
# include "nvim/file_search.h"
# include "nvim/getchar.h"
# include "nvim/grid.h"
# include "nvim/mark.h"
# include "nvim/msgpack_rpc/channel.h"
# include "nvim/option.h"
# include "nvim/os/os.h"
# include "nvim/quickfix.h"
# include "nvim/regexp.h"
# include "nvim/register.h"
# include "nvim/search.h"
# include "nvim/spell.h"
# include "nvim/tag.h"
# include "nvim/window.h"
// Free everything that we allocated.
// Can be used to detect memory leaks, e.g., with ccmalloc.
// NOTE: This is tricky! Things are freed that functions depend on. Don't be
// surprised if Vim crashes...
// Some things can't be freed, esp. things local to a library function.
void free_all_mem(void)
{
buf_T *buf, *nextbuf;
// When we cause a crash here it is caught and Vim tries to exit cleanly.
// Don't try freeing everything again.
if (entered_free_all_mem) {
return;
}
entered_free_all_mem = true;
// Don't want to trigger autocommands from here on.
block_autocmds();
// Close all tabs and windows. Reset 'equalalways' to avoid redraws.
p_ea = false;
if (first_tabpage != NULL && first_tabpage->tp_next != NULL) {
do_cmdline_cmd("tabonly!");
}
// Free all spell info.
spell_free_all();
// Clear user commands (before deleting buffers).
ex_comclear(NULL);
if (curbuf != NULL) {
// Clear menus.
do_cmdline_cmd("aunmenu *");
do_cmdline_cmd("tlunmenu *");
do_cmdline_cmd("menutranslate clear");
// Clear mappings, abbreviations, breakpoints.
// NB: curbuf not used with local=false arg
map_clear_mode(curbuf, MAP_ALL_MODES, false, false);
map_clear_mode(curbuf, MAP_ALL_MODES, false, true);
do_cmdline_cmd("breakdel *");
do_cmdline_cmd("profdel *");
do_cmdline_cmd("set keymap=");
}
free_titles();
free_findfile();
// Obviously named calls.
free_all_autocmds();
free_all_marks();
alist_clear(&global_alist);
free_homedir();
free_users();
free_search_patterns();
free_old_sub();
free_last_insert();
free_insexpand_stuff();
free_prev_shellcmd();
free_regexp_stuff();
free_tag_stuff();
free_cd_dir();
free_signs();
set_expr_line(NULL);
if (curtab != NULL) {
diff_clear(curtab);
}
clear_sb_text(true); // free any scrollback text
// Free some global vars.
xfree(last_cmdline);
xfree(new_last_cmdline);
set_keep_msg(NULL, 0);
// Clear cmdline history.
p_hi = 0;
init_history();
free_quickfix();
// Close all script inputs.
close_all_scripts();
if (curwin != NULL) {
// Destroy all windows. Must come before freeing buffers.
win_free_all();
}
// Free all option values. Must come after closing windows.
free_all_options();
// Free all buffers. Reset 'autochdir' to avoid accessing things that
// were freed already.
// Must be after eval_clear to avoid it trying to access b:changedtick after
// freeing it.
p_acd = false;
for (buf = firstbuf; buf != NULL;) {
bufref_T bufref;
set_bufref(&bufref, buf);
nextbuf = buf->b_next;
// Since options (in addition to other stuff) have been freed above we need to ensure no
// callbacks are called, so free them before closing the buffer.
buf_free_callbacks(buf);
close_buffer(NULL, buf, DOBUF_WIPE, false, false);
// Didn't work, try next one.
buf = bufref_valid(&bufref) ? nextbuf : firstbuf;
}
// Clear registers.
clear_registers();
ResetRedobuff();
ResetRedobuff();
// highlight info
free_highlight();
reset_last_sourcing();
if (first_tabpage != NULL) {
free_tabpage(first_tabpage);
first_tabpage = NULL;
}
// message history
msg_hist_clear(0);
channel_free_all_mem();
eval_clear();
api_extmark_free_all_mem();
ctx_free_all();
map_destroy(int, &buffer_handles);
map_destroy(int, &window_handles);
map_destroy(int, &tabpage_handles);
// free screenlines (can't display anything now!)
grid_free_all_mem();
stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size);
xfree(tab_page_click_defs);
clear_hl_tables(false);
check_quickfix_busy();
decor_free_all_mem();
drawline_free_all_mem();
if (ui_client_channel_id) {
ui_client_free_all_mem();
}
remote_ui_free_all_mem();
ui_free_all_mem();
ui_comp_free_all_mem();
nlua_free_all_mem();
rpc_free_all_mem();
// should be last, in case earlier free functions deallocates arenas
arena_free_reuse_blks();
}
#endif