From da9b0abc67a936021a4ecf7634395db860edcab1 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Thu, 14 Oct 2021 00:40:46 +0100 Subject: [PATCH] feat(:source, nvim_exec): defer script item creation until s:var access For anonymous scripts, defer the creation of script items until an attempt to access a script-local variable is made. This dramatically reduces the number of script items created when using lots of vim.cmd and nvim_exec especially. This will mean usage fails until a script-local variable access is first made. --- src/nvim/eval.c | 6 ++++- src/nvim/ex_cmds2.c | 30 ++----------------------- src/nvim/ex_cmds2.h | 25 +++++++++++++++++++++ test/functional/api/vim_spec.lua | 22 ++++++++++++++++++ test/functional/ex_cmds/source_spec.lua | 10 ++++++--- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ac659fbda4..2709f24998 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9191,8 +9191,12 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons } else if (*name == 'l' && funccal != NULL) { // local variable *d = &funccal->l_vars; } else if (*name == 's' // script variable - && current_sctx.sc_sid > 0 + && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR) && current_sctx.sc_sid <= ga_scripts.ga_len) { + // For anonymous scripts without a script item, create one now so script vars can be used + if (current_sctx.sc_sid == SID_STR) { + new_script_item(NULL, ¤t_sctx.sc_sid); + } *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c421d3991c..4d26e16104 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -52,33 +52,7 @@ #include "nvim/version.h" #include "nvim/window.h" - /// Growarray to store info about already sourced scripts. -/// Also store the dev/ino, so that we don't have to stat() each -/// script when going through the list. -typedef struct scriptitem_S { - char_u *sn_name; - bool file_id_valid; - FileID file_id; - bool sn_prof_on; ///< true when script is/was profiled - bool sn_pr_force; ///< forceit: profile functions in this script - proftime_T sn_pr_child; ///< time set when going into first child - int sn_pr_nest; ///< nesting for sn_pr_child - // profiling the script as a whole - int sn_pr_count; ///< nr of times sourced - proftime_T sn_pr_total; ///< time spent in script + children - proftime_T sn_pr_self; ///< time spent in script itself - proftime_T sn_pr_start; ///< time at script start - proftime_T sn_pr_children; ///< time in children after script start - // profiling the script per line - garray_T sn_prl_ga; ///< things stored for every line - proftime_T sn_prl_start; ///< start time for current line - proftime_T sn_prl_children; ///< time spent in children for this line - proftime_T sn_prl_wait; ///< wait start time for current line - linenr_T sn_prl_idx; ///< index of line being timed; -1 if none - int sn_prl_execed; ///< line being timed was executed -} scriptitem_T; - static garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL }; #define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) @@ -1944,7 +1918,7 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) /// @param name File name of the script. NULL for anonymous :source. /// @param[out] sid_out SID of the new item. /// @return pointer to the created script item. -static scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) +scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) { static scid_T last_current_SID = 0; const scid_T sid = ++last_current_SID; @@ -1978,7 +1952,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char sourcing_lnum = 0; const sctx_T save_current_sctx = current_sctx; - new_script_item(NULL, ¤t_sctx.sc_sid); + current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; funccal_entry_T entry; diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index de4e1429b7..d426ff28f8 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -16,6 +16,31 @@ #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! +/// Also store the dev/ino, so that we don't have to stat() each +/// script when going through the list. +typedef struct scriptitem_S { + char_u *sn_name; + bool file_id_valid; + FileID file_id; + bool sn_prof_on; ///< true when script is/was profiled + bool sn_pr_force; ///< forceit: profile functions in this script + proftime_T sn_pr_child; ///< time set when going into first child + int sn_pr_nest; ///< nesting for sn_pr_child + // profiling the script as a whole + int sn_pr_count; ///< nr of times sourced + proftime_T sn_pr_total; ///< time spent in script + children + proftime_T sn_pr_self; ///< time spent in script itself + proftime_T sn_pr_start; ///< time at script start + proftime_T sn_pr_children; ///< time in children after script start + // profiling the script per line + garray_T sn_prl_ga; ///< things stored for every line + proftime_T sn_prl_start; ///< start time for current line + proftime_T sn_prl_children; ///< time spent in children for this line + proftime_T sn_prl_wait; ///< wait start time for current line + linenr_T sn_prl_idx; ///< index of line being timed; -1 if none + int sn_prl_execed; ///< line being timed was executed +} scriptitem_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds2.h.generated.h" #endif diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f878a50a26..d8914a3ab7 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -89,6 +89,14 @@ describe('API', function() it(':verbose set {option}?', function() nvim('exec', 'set nowrap', false) + eq('nowrap\n\tLast set from anonymous :source', + nvim('exec', 'verbose set wrap?', true)) + + -- Using script var to force creation of a script item + nvim('exec', [[ + let s:a = 1 + set nowrap + ]], false) eq('nowrap\n\tLast set from anonymous :source (script id 1)', nvim('exec', 'verbose set wrap?', true)) end) @@ -155,6 +163,20 @@ describe('API', function() let s:pirate = 'script-scoped varrrrr' call nvim_exec('echo s:pirate', 1) ]], false)) + + -- Script items are created only on script var access + eq('1\n0', nvim('exec', [[ + echo expand("")->empty() + let s:a = 123 + echo expand("")->empty() + ]], true)) + + eq('1\n0', nvim('exec', [[ + echo expand("")->empty() + function s:a() abort + endfunction + echo expand("")->empty() + ]], true)) end) it('non-ASCII input', function() diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 8077b22e56..fa650d611b 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -26,13 +26,17 @@ describe(':source', function() \ k: "v" "\ (o_o) \ } + let c = expand("")->empty() let s:s = 0zbeef.cafe - let c = s:s]]) + let d = s:s]]) command('source') eq('2', meths.exec('echo a', true)) eq("{'k': 'v'}", meths.exec('echo b', true)) - eq("0zBEEFCAFE", meths.exec('echo c', true)) + + -- Script items are created only on script var access + eq("1", meths.exec('echo c', true)) + eq("0zBEEFCAFE", meths.exec('echo d', true)) exec('set cpoptions+=C') eq('Vim(let):E15: Invalid expression: #{', exc_exec('source')) @@ -62,7 +66,7 @@ describe(':source', function() feed_command(':source') eq('4', meths.exec('echo a', true)) eq("{'K': 'V'}", meths.exec('echo b', true)) - eq("3_C()", meths.exec('echo D()', true)) + eq("1_C()", meths.exec('echo D()', true)) -- Source last line only feed_command(':$source')