From: Yasuhiro Matsumoto Date: Mon, 2 Mar 2026 20:41:44 +0000 (+0000) Subject: patch 9.2.0096: has() function is slow due to linear feature scan X-Git-Tag: v9.2.0096^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=327e0e34c907abafbf356700705a3676c036ca65;p=thirdparty%2Fvim.git patch 9.2.0096: has() function is slow due to linear feature scan Problem: The has() function is slow because it performs a linear scan of the feature list for every call. Solution: Move common runtime checks and the patch-version parser to the beginning of the f_has() function (Yasuhiro Matsumoto). closes: #19550 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/src/evalfunc.c b/src/evalfunc.c index 139d940cc2..3c872af56a 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -7760,214 +7760,217 @@ f_has(typval_T *argvars, typval_T *rettv) return; name = tv_get_string(&argvars[0]); - for (i = 0; has_list[i].name != NULL; ++i) - if (STRICMP(name, has_list[i].name) == 0) + + // Fast-path: check features not in has_list[] first to avoid the full + // linear scan for very common queries like has('patch-...'). + if (STRNICMP(name, "patch", 5) == 0) + { + x = TRUE; + if (name[5] == '-' + && STRLEN(name) >= 11 + && (name[6] >= '1' && name[6] <= '9')) { - x = TRUE; - n = has_list[i].present; - break; + char *end; + int major, minor; + + // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc. + // Not for patch-9.10.5. + major = (int)strtoul((char *)name + 6, &end, 10); + if (*end == '.' && vim_isdigit(end[1]) + && end[2] == '.' && vim_isdigit(end[3])) + { + minor = atoi(end + 1); + + // Expect "patch-9.9.01234". + n = (major < VIM_VERSION_MAJOR + || (major == VIM_VERSION_MAJOR + && (minor < VIM_VERSION_MINOR + || (minor == VIM_VERSION_MINOR + && has_patch(atoi(end + 3)))))); + } } + else if (SAFE_isdigit(name[5])) + n = has_patch(atoi((char *)name + 5)); + } + else if (STRICMP(name, "vim_starting") == 0) + { + x = TRUE; + n = (starting != 0); + } + else if (STRICMP(name, "ttyin") == 0) + { + x = TRUE; + n = mch_input_isatty(); + } + else if (STRICMP(name, "ttyout") == 0) + { + x = TRUE; + n = stdout_isatty; + } + else if (STRICMP(name, "multi_byte_encoding") == 0) + { + x = TRUE; + n = has_mbyte; + } + else if (STRICMP(name, "gui_running") == 0) + { + x = TRUE; +#ifdef FEAT_GUI + n = (gui.in_use || gui.starting); +#endif + } + else if (STRICMP(name, "browse") == 0) + { + x = TRUE; +#if defined(FEAT_GUI) && defined(FEAT_BROWSE) + n = gui.in_use; // gui_mch_browse() works when GUI is running +#endif + } + else if (STRICMP(name, "syntax_items") == 0) + { + x = TRUE; +#ifdef FEAT_SYN_HL + n = syntax_present(curwin); +#endif + } + else if (STRICMP(name, "vcon") == 0) + { + x = TRUE; +#ifdef FEAT_VTP + n = is_term_win32() && has_vtp_working(); +#endif + } + else if (STRICMP(name, "netbeans_enabled") == 0) + { + x = TRUE; +#ifdef FEAT_NETBEANS_INTG + n = netbeans_active(); +#endif + } + else if (STRICMP(name, "mouse_gpm_enabled") == 0) + { + x = TRUE; +#ifdef FEAT_MOUSE_GPM + n = gpm_enabled(); +#endif + } + else if (STRICMP(name, "conpty") == 0) + { + x = TRUE; +#if defined(FEAT_TERMINAL) && defined(MSWIN) + n = use_conpty(); +#endif + } + else if (STRICMP(name, "clipboard_working") == 0) + { + x = TRUE; +#ifdef FEAT_CLIPBOARD + n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available; +#endif + } + else if (STRICMP(name, "unnamedplus") == 0) + { + x = TRUE; +#ifdef FEAT_CLIPBOARD + // The + register is available when clipmethod is set to a provider, + // but becomes unavailable if on a platform that doesn't support it + // and clipmethod is "none". + // (Windows, MacOS). +# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD) + n = TRUE; +# elif defined(FEAT_EVAL) + if (clipmethod == CLIPMETHOD_PROVIDER) + n = TRUE; + else + n = FALSE; +# else + n = FALSE; +# endif +#endif + } - // features also in has_list[] but sometimes enabled at runtime - if (x == TRUE && n == FALSE) + // Look up in has_list[] only if not already handled above. + if (x == FALSE) { - if (0) + for (i = 0; has_list[i].name != NULL; ++i) + if (STRICMP(name, has_list[i].name) == 0) + { + x = TRUE; + n = has_list[i].present; + break; + } + + // features also in has_list[] but sometimes enabled at runtime + if (x == TRUE && n == FALSE) { - // intentionally empty - } + if (0) + { + // intentionally empty + } #ifdef VIMDLL - else if (STRICMP(name, "filterpipe") == 0) - n = gui.in_use || gui.starting; + else if (STRICMP(name, "filterpipe") == 0) + n = gui.in_use || gui.starting; #endif #if defined(USE_ICONV) && defined(DYNAMIC_ICONV) - else if (STRICMP(name, "iconv") == 0) - n = iconv_enabled(FALSE); + else if (STRICMP(name, "iconv") == 0) + n = iconv_enabled(FALSE); #endif #ifdef DYNAMIC_LUA - else if (STRICMP(name, "lua") == 0) - n = lua_enabled(FALSE); + else if (STRICMP(name, "lua") == 0) + n = lua_enabled(FALSE); #endif #ifdef DYNAMIC_MZSCHEME - else if (STRICMP(name, "mzscheme") == 0) - n = mzscheme_enabled(FALSE); + else if (STRICMP(name, "mzscheme") == 0) + n = mzscheme_enabled(FALSE); #endif #ifdef DYNAMIC_PERL - else if (STRICMP(name, "perl") == 0) - n = perl_enabled(FALSE); + else if (STRICMP(name, "perl") == 0) + n = perl_enabled(FALSE); #endif #ifdef DYNAMIC_PYTHON - else if (STRICMP(name, "python") == 0) - n = python_enabled(FALSE); + else if (STRICMP(name, "python") == 0) + n = python_enabled(FALSE); #endif #ifdef DYNAMIC_PYTHON3 - else if (STRICMP(name, "python3") == 0) - n = python3_enabled(FALSE); + else if (STRICMP(name, "python3") == 0) + n = python3_enabled(FALSE); #endif #if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3) - else if (STRICMP(name, "pythonx") == 0) - { + else if (STRICMP(name, "pythonx") == 0) + { # if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3) - if (p_pyx == 0) - n = python3_enabled(FALSE) || python_enabled(FALSE); - else if (p_pyx == 3) - n = python3_enabled(FALSE); - else if (p_pyx == 2) - n = python_enabled(FALSE); + if (p_pyx == 0) + n = python3_enabled(FALSE) || python_enabled(FALSE); + else if (p_pyx == 3) + n = python3_enabled(FALSE); + else if (p_pyx == 2) + n = python_enabled(FALSE); # elif defined(DYNAMIC_PYTHON) - n = python_enabled(FALSE); + n = python_enabled(FALSE); # elif defined(DYNAMIC_PYTHON3) - n = python3_enabled(FALSE); + n = python3_enabled(FALSE); # endif - } + } #endif #ifdef DYNAMIC_RUBY - else if (STRICMP(name, "ruby") == 0) - n = ruby_enabled(FALSE); + else if (STRICMP(name, "ruby") == 0) + n = ruby_enabled(FALSE); #endif #ifdef DYNAMIC_TCL - else if (STRICMP(name, "tcl") == 0) - n = tcl_enabled(FALSE); + else if (STRICMP(name, "tcl") == 0) + n = tcl_enabled(FALSE); #endif #ifdef DYNAMIC_SODIUM - else if (STRICMP(name, "sodium") == 0) - n = sodium_enabled(FALSE); + else if (STRICMP(name, "sodium") == 0) + n = sodium_enabled(FALSE); #endif #if defined(FEAT_TERMINAL) && defined(MSWIN) - else if (STRICMP(name, "terminal") == 0) - n = terminal_enabled(); + else if (STRICMP(name, "terminal") == 0) + n = terminal_enabled(); #endif #ifdef DYNAMIC_GPM - else if (STRICMP(name, "mouse_gpm") == 0) - n = gpm_available(); -#endif - } - - // features not in has_list[] - if (x == FALSE) - { - if (STRNICMP(name, "patch", 5) == 0) - { - x = TRUE; - if (name[5] == '-' - && STRLEN(name) >= 11 - && (name[6] >= '1' && name[6] <= '9')) - { - char *end; - int major, minor; - - // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc. - // Not for patch-9.10.5. - major = (int)strtoul((char *)name + 6, &end, 10); - if (*end == '.' && vim_isdigit(end[1]) - && end[2] == '.' && vim_isdigit(end[3])) - { - minor = atoi(end + 1); - - // Expect "patch-9.9.01234". - n = (major < VIM_VERSION_MAJOR - || (major == VIM_VERSION_MAJOR - && (minor < VIM_VERSION_MINOR - || (minor == VIM_VERSION_MINOR - && has_patch(atoi(end + 3)))))); - } - } - else if (SAFE_isdigit(name[5])) - n = has_patch(atoi((char *)name + 5)); - } - else if (STRICMP(name, "vim_starting") == 0) - { - x = TRUE; - n = (starting != 0); - } - else if (STRICMP(name, "ttyin") == 0) - { - x = TRUE; - n = mch_input_isatty(); - } - else if (STRICMP(name, "ttyout") == 0) - { - x = TRUE; - n = stdout_isatty; - } - else if (STRICMP(name, "multi_byte_encoding") == 0) - { - x = TRUE; - n = has_mbyte; - } - else if (STRICMP(name, "gui_running") == 0) - { - x = TRUE; -#ifdef FEAT_GUI - n = (gui.in_use || gui.starting); -#endif - } - else if (STRICMP(name, "browse") == 0) - { - x = TRUE; -#if defined(FEAT_GUI) && defined(FEAT_BROWSE) - n = gui.in_use; // gui_mch_browse() works when GUI is running -#endif - } - else if (STRICMP(name, "syntax_items") == 0) - { - x = TRUE; -#ifdef FEAT_SYN_HL - n = syntax_present(curwin); -#endif - } - else if (STRICMP(name, "vcon") == 0) - { - x = TRUE; -#ifdef FEAT_VTP - n = is_term_win32() && has_vtp_working(); -#endif - } - else if (STRICMP(name, "netbeans_enabled") == 0) - { - x = TRUE; -#ifdef FEAT_NETBEANS_INTG - n = netbeans_active(); -#endif - } - else if (STRICMP(name, "mouse_gpm_enabled") == 0) - { - x = TRUE; -#ifdef FEAT_MOUSE_GPM - n = gpm_enabled(); -#endif - } - else if (STRICMP(name, "conpty") == 0) - { - x = TRUE; -#if defined(FEAT_TERMINAL) && defined(MSWIN) - n = use_conpty(); -#endif - } - else if (STRICMP(name, "clipboard_working") == 0) - { - x = TRUE; -#ifdef FEAT_CLIPBOARD - n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available; -#endif - } - else if (STRICMP(name, "unnamedplus") == 0) - { - x = TRUE; -#ifdef FEAT_CLIPBOARD - // The + register is available when clipmethod is set to a provider, - // but becomes unavailable if on a platform that doesn't support it - // and clipmethod is "none". - // (Windows, MacOS). -# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD) - n = TRUE; -# elif defined(FEAT_EVAL) - if (clipmethod == CLIPMETHOD_PROVIDER) - n = TRUE; - else - n = FALSE; -# else - n = FALSE; -# endif + else if (STRICMP(name, "mouse_gpm") == 0) + n = gpm_available(); #endif } } diff --git a/src/version.c b/src/version.c index dde1211ce6..31e444098f 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 96, /**/ 95, /**/