From 0d4d285bd2c19697164175280b32bf93e3a7b1da Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 17 Apr 2026 19:10:20 -0400 Subject: perf(vim.fn): call Lua-implemented vim.fn.xx() directly #39166 Problem: - Builtin "Vimscript" functions (f_xx) are mostly implemented in C. Partly that's because there is some boilerplate required to call out to Lua. - Calls to `vim.fn.foo()` always marshall over the Lua <=> Vimscript ("typval") bridge, even if `fn.foo()` is implemented entirely in Lua: ``` Lua => typval => Object => Lua => Object => typval => Lua. ``` Solution: Functions declared in eval.lua with `func_lua` are implemented in entirely in Lua (`_core/vimfn.lua`). - `gen_eval.lua` wires `func_lua` entries to `lua_wrapper`, which handles the typval conversion for Vimscript callers (slow path). - `nlua_call()` detects `func_lua` functions and calls the Lua implementation directly. This eliminates all conversion overhead for Lua callers (fast path). - Validate at build-time that `func`, `func_float`, and `func_lua` are mutually exclusive. - Migrate `hostname()` as a toy example, to show the idea. --- src/nvim/lua/executor.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/nvim/lua/executor.c') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 7c4e42420a..4cd81e365d 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1207,6 +1207,7 @@ static bool viml_func_is_fast(const char *name) return false; } +/// "vim.call()", aka "vim.fn". int nlua_call(lua_State *lstate) { Error err = ERROR_INIT; @@ -1224,6 +1225,27 @@ int nlua_call(lua_State *lstate) return luaL_error(lstate, "Function called with too many arguments"); } + // Fast path: if the vimfn is defined with `func_lua`, call the Lua function + // directly without Lua→typval→Object→Lua round-trip. + const char *func_lua = find_internal_func_lua(name); + if (func_lua) { + lua_getglobal(lstate, "require"); + lua_pushliteral(lstate, "vim._core.vimfn"); + if (lua_pcall(lstate, 1, 1, 0) != 0) { + return lua_error(lstate); + } + lua_getfield(lstate, -1, func_lua); + lua_remove(lstate, -2); // remove module table + // Push args (still on the stack as Lua values). + for (int j = 0; j < nargs; j++) { + lua_pushvalue(lstate, j + 2); + } + if (lua_pcall(lstate, nargs, 1, 0) != 0) { + return lua_error(lstate); + } + return 1; + } + typval_T vim_args[MAX_FUNC_ARGS + 1]; int i = 0; // also used for freeing the variables for (; i < nargs; i++) { -- cgit