diff options
| author | Justin M. Keyes <justinkz@gmail.com> | 2026-04-17 19:10:20 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-17 19:10:20 -0400 |
| commit | 0d4d285bd2c19697164175280b32bf93e3a7b1da (patch) | |
| tree | 0aa20f3e342279e9cba36e5c8daa0d38b2507c8d /src/nvim/lua/executor.c | |
| parent | 03193e2963a6f9333afc3090ad2c8002b648fe11 (diff) | |
| download | rneovim-0d4d285bd2c19697164175280b32bf93e3a7b1da.tar.gz rneovim-0d4d285bd2c19697164175280b32bf93e3a7b1da.tar.bz2 rneovim-0d4d285bd2c19697164175280b32bf93e3a7b1da.zip | |
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.
Diffstat (limited to 'src/nvim/lua/executor.c')
| -rw-r--r-- | src/nvim/lua/executor.c | 22 |
1 files changed, 22 insertions, 0 deletions
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++) { |