diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index fc8c9ef445..25f80758d2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -7893,7 +7893,8 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) uint64_t chan_id = (uint64_t)argvars[0].vval.v_number; const char *method = tv_get_string(&argvars[1]); - Object result = rpc_send_call(chan_id, method, args, &err); + ArenaMem res_mem = NULL; + Object result = rpc_send_call(chan_id, method, args, &res_mem, &err); if (l_provider_call_nesting) { current_sctx = save_current_sctx; @@ -7928,7 +7929,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } end: - api_free_object(result); + arena_mem_free(res_mem, NULL); api_clear_error(&err); } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index bd06123d5f..215aae9430 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1016,10 +1016,11 @@ static int nlua_rpc(lua_State *lstate, bool request) } if (request) { - Object result = rpc_send_call(chan_id, name, args, &err); + ArenaMem res_mem = NULL; + Object result = rpc_send_call(chan_id, name, args, &res_mem, &err); if (!ERROR_SET(&err)) { nlua_push_Object(lstate, result, false); - api_free_object(result); + arena_mem_free(res_mem, NULL); } } else { if (!rpc_send_event(chan_id, name, args)) { diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 19a520636e..32c4502231 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -524,6 +524,87 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) } } +#define ARENA_BLOCK_SIZE 4096 + +void arena_start(Arena *arena, ArenaMem *reuse_blk) +{ + if (reuse_blk && *reuse_blk) { + arena->cur_blk = (char *)*reuse_blk; + *reuse_blk = NULL; + } else { + arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + } + arena->pos = 0; + arena->size = ARENA_BLOCK_SIZE; + // address is the same as as (struct consumed_blk *)arena->cur_blk + struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); + assert((char *)blk == (char *)arena->cur_blk); + blk->prev = NULL; +} + +/// Finnish the allocations in an arena. +/// +/// This does not immedately 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; +} + +void *arena_alloc(Arena *arena, size_t size, bool align) +{ + if (align) { + arena->pos = (arena->pos + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); + } + if (arena->pos + size > arena->size) { + if (size > (arena->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. + char *alloc = xmalloc(size + sizeof(struct consumed_blk)); + 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 + sizeof(struct consumed_blk)); + } else { + struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; + arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + arena->pos = 0; + arena->size = ARENA_BLOCK_SIZE; + struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); + blk->prev = prev_blk; + } + } + + char *mem = arena->cur_blk + arena->pos; + arena->pos += size; + return mem; +} + +void arena_mem_free(ArenaMem mem, ArenaMem *reuse_blk) +{ + 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 (reuse_blk && *reuse_blk == NULL && b != NULL) { + *reuse_blk = b; + b = b->prev; + (*reuse_blk)->prev = NULL; + } + + while (b) { + struct consumed_blk *prev = b->prev; + xfree(b); + b = prev; + } +} + #if defined(EXITFREE) # include "nvim/buffer.h" diff --git a/src/nvim/memory.h b/src/nvim/memory.h index a4be2643d8..0c048e1b5b 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -37,6 +37,20 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif +typedef struct consumed_blk { + struct consumed_blk *prev; +} *ArenaMem; + +#define ARENA_ALIGN sizeof(void *) + +typedef struct { + char *cur_blk; + size_t pos, size; +} Arena; + +// inits an empty arena. use arena_start() to actually allocate space! +#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 } + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.h.generated.h" #endif diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 79a9e1082d..b4f39c4c61 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -26,6 +26,7 @@ #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/unpacker.h" #include "nvim/os/input.h" #include "nvim/os_unix.h" #include "nvim/ui.h" @@ -113,7 +114,8 @@ bool rpc_send_event(uint64_t id, const char *name, Array args) /// @param args Array with method arguments /// @param[out] error True if the return value is an error /// @return Whatever the remote method returned -Object rpc_send_call(uint64_t id, const char *method_name, Array args, Error *err) +Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem *result_mem, + Error *err) { Channel *channel = NULL; @@ -130,7 +132,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, Error *er send_request(channel, request_id, method_name, args); // Push the frame - ChannelCallFrame frame = { request_id, false, false, NIL }; + ChannelCallFrame frame = { request_id, false, false, NIL, NULL }; kv_push(rpc->call_stack, &frame); LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned); (void)kv_pop(rpc->call_stack); @@ -155,11 +157,15 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, Error *er api_set_error(err, kErrorTypeException, "%s", "unknown error"); } - api_free_object(frame.result); + // frame.result was allocated in an arena + arena_mem_free(frame.result_mem, &rpc->unpacker->reuse_blk); + frame.result_mem = NULL; } channel_decref(channel); + *result_mem = frame.result_mem; + return frame.errored ? NIL : frame.result; } @@ -249,17 +255,17 @@ static void parse_msgpack(Channel *channel) if (frame->errored) { frame->result = p->error; // TODO(bfredl): p->result should not even be decoded - api_free_object(p->result); + // api_free_object(p->result); } else { frame->result = p->result; } + frame->result_mem = arena_finish(&p->arena); } else { log_client_msg(channel->id, p->type == kMessageTypeRequest, p->handler.name); Object res = p->result; if (p->result.type != kObjectTypeArray) { chan_close_with_error(channel, "msgpack-rpc request args has to be an array", LOGLVL_ERR); - api_free_object(p->result); return; } Array arg = res.data.array; @@ -282,7 +288,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) if (!p->handler.fn) { send_error(channel, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); - api_free_array(args); + arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); return; } @@ -291,6 +297,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) evdata->channel = channel; evdata->handler = p->handler; evdata->args = args; + evdata->used_mem = arena_finish(&p->arena); evdata->request_id = p->request_id; channel_incref(channel); if (p->handler.fast) { @@ -346,7 +353,8 @@ static void request_event(void **argv) } free_ret: - api_free_array(e->args); + // e->args is allocated in an arena + arena_mem_free(e->used_mem, &channel->rpc.unpacker->reuse_blk); channel_decref(channel); xfree(e); api_clear_error(&error); @@ -523,6 +531,7 @@ static void exit_event(void **argv) void rpc_free(Channel *channel) { remote_ui_disconnect(channel->id); + unpacker_teardown(channel->rpc.unpacker); xfree(channel->rpc.unpacker); // Unsubscribe from all events @@ -543,7 +552,6 @@ static void chan_close_with_error(Channel *channel, char *msg, int loglevel) ChannelCallFrame *frame = kv_A(channel->rpc.call_stack, i); frame->returned = true; frame->errored = true; - api_free_object(frame->result); frame->result = STRING_OBJ(cstr_to_string(msg)); } diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h index 4dc3c7f22d..e622ebddf5 100644 --- a/src/nvim/msgpack_rpc/channel_defs.h +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -9,15 +9,16 @@ #include "nvim/api/private/dispatch.h" #include "nvim/event/process.h" #include "nvim/event/socket.h" -#include "nvim/msgpack_rpc/unpacker.h" #include "nvim/vim.h" typedef struct Channel Channel; +typedef struct Unpacker Unpacker; typedef struct { uint32_t request_id; bool returned, errored; Object result; + ArenaMem result_mem; } ChannelCallFrame; typedef struct { @@ -26,6 +27,7 @@ typedef struct { MsgpackRpcRequestHandler handler; Array args; uint32_t request_id; + ArenaMem used_mem; } RequestEvent; typedef struct { diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index e60d9f220f..419349e74d 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -3,6 +3,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/log.h" +#include "nvim/memory.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/unpacker.h" @@ -10,6 +11,10 @@ # include "msgpack_rpc/unpacker.c.generated.h" #endif +#define kv_fixsize_arena(a, v, s) \ + ((v).capacity = (s), \ + (v).items = (void *)arena_alloc(a, sizeof((v).items[0]) * (v).capacity, true)) + Object unpack(const char *data, size_t size, Error *err) { Unpacker unpacker; @@ -34,7 +39,7 @@ Object unpack(const char *data, size_t size, Error *err) static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) { - Unpacker *unpacker = parser->data.p; + Unpacker *p = parser->data.p; Object *result = NULL; String *key_location = NULL; @@ -69,7 +74,7 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) abort(); } } else { - result = &unpacker->result; + result = &p->result; } switch (node->tok.type) { @@ -88,16 +93,17 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) case MPACK_TOKEN_FLOAT: *result = FLOAT_OBJ(mpack_unpack_float(node->tok)); break; + case MPACK_TOKEN_BIN: case MPACK_TOKEN_STR: { - String str = { .data = xmallocz(node->tok.length), .size = node->tok.length }; - + char *mem = arena_alloc(&p->arena, node->tok.length + 1, false); + mem[node->tok.length] = NUL; + String str = { .data = mem, .size = node->tok.length }; if (key_location) { *key_location = str; } else { *result = STRING_OBJ(str); } - node->data[0].p = str.data; break; } @@ -105,7 +111,6 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) // handled in chunk; but save result location node->data[0].p = result; break; - case MPACK_TOKEN_CHUNK: assert(parent); if (parent->tok.type == MPACK_TOKEN_STR || parent->tok.type == MPACK_TOKEN_BIN) { @@ -120,12 +125,12 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) *res = NIL; break; } - memcpy(unpacker->ext_buf + parent->pos, + memcpy(p->ext_buf + parent->pos, node->tok.data.chunk_ptr, node->tok.length); if (parent->pos + node->tok.length < parent->tok.length) { break; // EOF, let's get back to it later } - const char *buf = unpacker->ext_buf; + const char *buf = p->ext_buf; size_t size = parent->tok.length; mpack_token_t ext_tok; int status = mpack_rtoken(&buf, &size, &ext_tok); @@ -148,7 +153,7 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) case MPACK_TOKEN_ARRAY: { Array arr = KV_INITIAL_VALUE; - kv_resize(arr, node->tok.length); + kv_fixsize_arena(&p->arena, arr, node->tok.length); kv_size(arr) = node->tok.length; *result = ARRAY_OBJ(arr); node->data[0].p = result; @@ -156,12 +161,13 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node) } case MPACK_TOKEN_MAP: { Dictionary dict = KV_INITIAL_VALUE; - kv_resize(dict, node->tok.length); + kv_fixsize_arena(&p->arena, dict, node->tok.length); kv_size(dict) = node->tok.length; *result = DICTIONARY_OBJ(dict); node->data[0].p = result; break; } + default: abort(); } @@ -176,6 +182,15 @@ void unpacker_init(Unpacker *p) p->parser.data.p = p; mpack_tokbuf_init(&p->reader); p->unpack_error = (Error)ERROR_INIT; + + p->arena = (Arena)ARENA_EMPTY; + p->reuse_blk = NULL; +} + +void unpacker_teardown(Unpacker *p) +{ + arena_mem_free(p->reuse_blk, NULL); + arena_mem_free(arena_finish(&p->arena), NULL); } bool unpacker_parse_header(Unpacker *p) @@ -282,6 +297,7 @@ bool unpacker_advance(Unpacker *p) return false; } p->state = p->type == kMessageTypeResponse ? 1 : 2; + arena_start(&p->arena, &p->reuse_blk); } int result; diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h index bbd6b1ef4f..e0dc6f0a68 100644 --- a/src/nvim/msgpack_rpc/unpacker.h +++ b/src/nvim/msgpack_rpc/unpacker.h @@ -9,8 +9,10 @@ #include "mpack/object.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/memory.h" +#include "nvim/msgpack_rpc/channel_defs.h" -typedef struct { +struct Unpacker { mpack_parser_t parser; mpack_tokbuf_t reader; @@ -28,7 +30,11 @@ typedef struct { Object error; // error return Object result; // arg list or result Error unpack_error; -} Unpacker; + + Arena arena; + // one lenght free-list of reusable blocks + ArenaMem reuse_blk; +}; // unrecovareble error. unpack_error should be set! #define unpacker_closed(p) ((p)->state < 0) diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index c8083cddb1..3e24e892b8 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -144,8 +144,10 @@ static void tinput_wait_enqueue(void **argv) Error err = ERROR_INIT; ADD(args, STRING_OBJ(copy_string(keys))); // TODO(bfredl): could be non-blocking now with paste? - Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &err); + ArenaMem res_mem = NULL; + Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &res_mem, &err); consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; + arena_mem_free(res_mem, NULL); } else { consumed = input_enqueue(keys); } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index d66e57b13b..bdcb7af7e9 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -229,15 +229,9 @@ void ui_refresh(void) p_lz = save_p_lz; } else { Array args = ARRAY_DICT_INIT; - Error err = ERROR_INIT; ADD(args, INTEGER_OBJ((int)width)); ADD(args, INTEGER_OBJ((int)height)); - rpc_send_call(ui_client_channel_id, "nvim_ui_try_resize", args, &err); - - if (ERROR_SET(&err)) { - ELOG("ui_client resize: %s", err.msg); - } - api_clear_error(&err); + rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); } if (ext_widgets[kUIMessages]) {